Discovering Web Workers

In a web page, Javascript is single threaded. It's all good if your application runs small operations, that return quickly. However, if you need to do heavier, longer computations, your user interface could get less responsive, harming your user's experience. While the Javascript thread is busy processing your data, it cannot handle the users's clicks, or update the elements on the page. That's where Web Workers come save the day. They let you run code in a separate thread, leaving the Javascript in your page free to handle the user interactions.

Let's start with a quick example:

  • two animated squares, one using JS, the other using CSS (I'll let you guess which is which ;))
  • buttons to run a "big computation" (well... just a big loop in fact), either in the page thread or using a web worker

When the page's running the computation, the JS animated square stops spinning (and in some browsers even the CSS one). The Javascript thread is stuck in the "big computation", nothing else will happen until it is over (even just changing the button's label). Using a Worker, the square keep spining, buttons handle clicks instantly...

Delegate the big computation to Web Workers, update the screen when it sends you the results, you keep your UI responsive. Sounds perfect to crunch big numbers or search in big arrays of data on the client.

Where I can haz Web Worker

Before looking deeper into how Web Worker works, let's have a look at which browsers support them. On desktop, you'll need to find an alternative if you need to support IE before its 10th version. On mobile, it's android's default browser that causes problem.

So... how does it work ?

The Web Worker introduces the Worker class, which allows you to create workers. You just need to provide the path to the Javascript file containing the code the worker runs. If you prefer to use a String rather than an external script, you'll have to be a bit sneaky but it's doable.

// On the page
var worker = new Worker('js/my-worker.js');

Workers and your page communicate using messages. You use postMessage() to send a message to the other side and listen to message events to act upon the data you receive. Note that the object you send in the message are copied to the other side. Workers and your page do not share any objects.

// On the page
worker.addEventListener('message', function (event) {
  // Process the worker's result here (eg. update UI)
  display(event.data);
});
worker.addEventListener('error', function () {
  // Process the worker's error there
});
worker.postMessage(mydata);

// In the worker
worker.addEventListener('message', function (event) {
  // Do the heavy lifting here...
  var result = crunchNumbers(event.data);
  // ... And send the result back to the page
  postMessage(result);
} 

On the page side, the worker can also dispatch a 'error' message in case something wrong happened during its execution.

When you're done using a worker, you can end its execution using the terminate method on the page's side. Pretty handy to stop a worker polling a web page at regular intervals.

Inside the worker

Workers are completely separated from your page. They don't share any data with it and have a restricted list of function/objects in their global scope.

Starting with the bad news, you've got no access to the DOM in your worker, nor to window or document. And maybe the one you'll miss most, no console inside the worker to do logging (using postMessage and a listener that logs every message received on the page's side makes a quick replacement).

Looking at the bright side of things, XMLHTTPRequest is available. This means you can make query to your service, maybe to pull some data at regular intervals. The worker's also implement the WindowTimers interface, giving you access to the setTimeout/setInterval methods (and their clearTimeout/clearInterval counterparts).

You also have access to the navigator, a read-only version of location, and online/offline events. btoa and atob are there too, if you need to do base 64 encoding/decoding.

Sharing code with your worker

You might want to reuse some functions or classes from the code running on your page, maybe even use some 3rd party library. Good news here, inside the worker you can load external scripts using the importScripts() function. The path of your scripts must be relative to the worker's script (I tested quickly but data URI seemed to work too) and comply to the same origin policy.

If you're planning to import a 3rd party library, remember the API available inside the worker is limited. If the library you need uses window or document, you'll probably need to extend/patch it (hopefully the first option ;)), or find another one.

Wrapping it up!

Web Workers are a bit tricky to debug with no access to `console`, as well as the dev tools having trouble putting breakpoints in the code of the workers (in Firefox and Chrome, at least, unless I missed an experimental feature). The lack of support in IE9 and below, as well as the native android browser also requires you to have a fallback (delegating the big work to the server, or maybe processing your data in smaller chunks). They provide a powerfull feature, though, that allows you to keep your UI responsive while handling big computations in the browser, saving you some requests to the server (or maybe you're offline and can't make request to your server). Great to be able to do more on the client side, don't you think?

comments powered by Disqus