Web Workers in Angular is simple in theory, but it requires a bit of tinkering to get them to feel like a part of the application. This post intends to explain a method which can make Web Workers in Angular (almost) seamless.
With the introduction of web workers web developers finally had the ability to allow long running, CPU intensive operations in the browser. These types of operations would otherwise have caused the GUI thread (the only thread up until this point) to block, and the GUI to freeze.
However the web worker syntax specification is a bit awkward, especially when combined with angularjs. To maintain complete backward compatibility and to protect developers from common parallel programming pitfalls the workers have a VERY limited exchange of information with the main browser thread. The main and web worker thread only communicate through message passing. All messages between the two are deep cloned (transferable objects can be used to minimize the overhead associated with the cloning). All this is well and good, it protects us from ourselves. But it makes for awkward programming.
Keeping the main and the web worker threads completely separated mean that resources loaded in the main thread is not loaded in the web worker thread automatically. In an angular application it means that if an angular app is using a web worker, the angular context is not automatically accessible in the web worker. If we do not load angular explicitly in the web worker it will only be capable of standard javascript. Whats worse is that a web workers needs to be loaded from a URL! What this normally means is that the developer specifies the javascript file that contains the web worker code like this:
var worker = new Worker('worker.js')
By using an addition to the web specification, which allows us to create object URL’s, we can get around the requirement of loading the web workers from separate files. This addition to the standard allows us to generate URL’s for snippets if blobs. Blobs can be pretty much anything, but we are interested in text that can be executed as javascript. A good explanation of this technique can be found here.
var blobURL = URL.createObjectURL(new Blob([ "var i = 0;//web worker body" ], { type: 'application/javascript' })); var worker = new Worker(blobURL);
We can not, and do not want to, get around the fact that the main and worker threads have separate contexts. But we can mitigate this inconvenience by this by leveraging the powerful dependency injection framework that angular ships with.
I have created an attempt at this here: https://github.com/FredrikSandell/angular-workers. angular-workers provide an angular service which takes a list containing dependencies, a function and returns a web worker, called AngularWorker, based on the functions source. The process is visualized in the sequence diagram below.
Once the promise of an AngularWorker has been resolved the application is able to repeatedly use the initialized AngularWorker for heavy duty work. The angular worker communicates the result to the main thread through an angular promise. Within the web worker function we are ensured that an angular context exists and that the dependencies which we specified at initialization are present.
A potential use case for this is to perform CPU intensive processing of large AJAX responses without locking the main GUI thread. This is the problem which first made me investigate this issue. The angular services which are used in the web worker can be handled as any other service and can therefore be tested as any other part of the angular application!