Web working like a boss

Run any function in a separate web worker thread using ng-webworker.

I love using Web Worker API to speed up browser computation. For example, I show how to avoid a long pause while computing a large number of primes in an Angular primes example application. The speed up is very noticeable if a task can be meaningfully split into smaller chunks. At least a long computation will NOT freeze the browser's screen while the computation is running. Typically, I load separate code in web worker and start a long computation by posting a message. The result is sent back to the window using a message too.

1
2
3
4
5
6
7
8
9
10
11
// main script
var worker = new Worker('path/to/foo.js');
worker.postMessage('action', { ...; self.postMessage(result); });
worker.onmessage = function (event) {
console.log('web worker computed', event.data);
};
// worker script
self.onmessage(function (event) {
...
self.postMessage(result);
});

Usually I write my own window to worker communication tools, like self-addressed to simplify the communication. Recently the author of ng-webworker Matt Slocum @mslocum showed me his very useful utility. It is a wrapper for executing an arbitrary function (without access to the lexical scope variables) in a separate web worker with an AngularJS api. I highly recommend visiting the ng-webworker demo page. The library supports 3 interesting and useful features:

1: Running an existing stand alone script with existing postMessage and onmessage callbacks \ via promise-returning API. The main script from the example above becomes simply

1
2
var worker = new Webworker('path/to/foo.js');
worker.run('action').then(function (result) { ... });

2: Running a function from the main script in a newly created web worker. The function is serialized into a blob URI and used as a source for the web worker. It is not supported by IE of course, but the rest of the browsers run just fine. I find this extremely useful. Any pure function that does not need outside variables (neither globals nor accessed via lexical scope). Finding primes is a pretty good candidate for running separately without an access to the outside scope - using just the input arguments. I prefer wrapping the web worker implementation in a factory to shield the users from these implementation details

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function primes(n) {
var foundPrimes = [];
// find n primes, put into foundPrimes
return foundPrimes;
}
angular.module('Primes', ['ngWebworker'])
.app.factory('PrimeWorker', function (Webworker) {
// worker initialization before saves around 50ms
var worker = Webworker.create(primes);
return {
computePrimes: function (n) {
return worker.run(n);
}
};
})
.controller('PrimesController', function ($scope, PrimeWorker) {
$scope.primes = [];
PrimeWorker.computePrimes(10000).then(function (numbers) {
$scope.primes = numbers;
});
});

That is it. Angular does a nice job hiding the DOM updates after promise resolution. I found that pre-initialization of web workers saves around 50ms, thus I prefer to keep at least one worker around.

3: Finally, a very nice feature of the ng-webworker API are the functions that return the progress value during computation. AngularJS $q service supports .then chaining with 3 callbacks, the last being notify (progress) one. A long running computation can periodically use the notify callback to let the window thread know its progress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function primes(n) {
var foundPrimes = [];
// find n primes, put into foundPrimes
// periodically execute
notify(k / n * 100);
return foundPrimes;
}
...
.controller('PrimesController', function ($scope, PrimeWorker) {
$scope.primes = [];
PrimeWorker.computePrimes(10000).then(function (numbers) {
$scope.primes = numbers;
}, null /* error callback */, function (progress) {
// progress is % complete
// set progress bar
});
});

Overall, I feel this is a great library with focused API and very useful features. I am looking forward to using it, as I use Web Worker more to parallelize my code.