Multi-Threaded JavaScript with Web Workers

Threads

Web workers let you write true multi-threaded JavaScript, meaning different bits of your code can be running at the same time. Without web workers, all code runs on the UI thread. Even things that seem multi-threaded, like ajax callbacks, setTimeout and setInterval, are actually single threaded. They may be time delayed, but once they’re running, everything else has to wait its turn.

Why Web Workers?

For most occasions, single threaded JavaScript is just fine. But what happens when you have some really intensive code that takes a while to run? If it runs on the UI thread, it’ll block everything else — and not just other JavaScript. DOM updates are blocked, CSS animations freeze, user interactions (right clicking, text selection, etc.) do nothing. Basically, the browser tab locks up hard. Not good at all.

Check out this single threaded demo to witness this terribleness in action.

Now let’s use web workers to offload the intensive code to a background thread, giving the UI thread room to breath. Check out this multi-threaded demo. Much better!

A Simple Code Example

Using a web worker requires 2 separate JavaScript files (I’ll explain why later). One is executed in the UI thread. We’ll name it main.js:

worker = new Worker('worker.js');
worker.addEventListener('message', receiveMessage);

function receiveMessage(e) {
    console.log(e.data);
}

worker.postMessage('cowboy');

And the other (we’ll name it worker.js) will run in a background thread:

self.addEventListener('message', receiveMessage);

function receiveMessage(e) {
    self.postMessage('Sup, ' + e.data + '?');
}

main.js instantiates the worker object with the URL for worker.js (this example assumes they’re sitting side by side). Both files set up their own message listeners, each with a callback to their own receiveMessage function. This is how the UI thread and background thread communicate: via messaging. They both listen for messages from each other and can post messages to each other.

The final line of main.js is what kicks things off. Here’s the flow:

  1. main.js messages ‘cowboy’ to worker.js
  2. worker.js receives the message, handles it by doing a little string concatenation, then messages ‘Sup, cowboy?’ back to main.js
  3. main.js receives this message and handles it by logging ‘Sup, cowboy?’ to the console

Richer Messaging

The sample code above uses very simple data (just a string) for each message, but you can send more complex data like so:

worker.postMessage({
    operation: 'update',
    id: 41,
    occupation: 'cowboy',
    date: new Date()
});

In this case, the web worker could access the occupation value via e.data.occupation. Yeah, it’s just JSON. Also, you can’t send functions via messaging.

Terms and Conditions Apply

As mentioned before, web workers must be put into their own file. This is because their code lives in a completely separate context than any code that exists in the UI thread. It’s a different world and only messaging can cross the divide. There are no shared global variables. Even the data sent via postMessage is not shared; it’s cloned.

Web workers do not use window (they use self instead).

And here’s a biggie: web workers cannot access the DOM. After all, that’s the territory of the UI thread. Instead, your web worker should post a message to the UI thread telling it to make any DOM updates.

While these restrictions may seem like buzzkills, they actually make things much simpler. Multi-threading headaches found in other languages (like locking objects) don’t apply to JavaScript, thanks to its philosophy of separate contexts and not sharing objects.

Boiled down, multi-threading in JavaScript is a simple matter of making calls and handling callbacks — something JavaScript developers already do all the time.

Browser Support

Browser support is decent. IE10+, Android 2.1 and 4.4+ (yeah, it was removed for a while, weird eh?) and then all the other major players.

Web workers browser support

Debugging

By default, debugging web workers can be difficult. For example, put a breakpoint in your web worker, run your code, and… watch it be ignored. Fortunately, this is easy to fix. If you’re using Chrome, open up the dev tools, go to the Sources tab, and check this checkbox:

How to debug web workers

Now try again. You’ll see a second dev tools window pop up when your web worker is instantiated. Now you have a window into your background thread. Set your breakpoints there and debug to your heart’s content.

Parting Words

That should be enough to get you started with multi-threading in JavaScript. There’s more, of course, but it’s probably better saved for another time.

I’ll leave you with something fun: a board game played against the computer (hint: it’s Connect Four). Yep, I’ve blogged about this one before. The computer can take a while to think on higher difficulty levels, so I offload all of that thinking to a web worker. This allows the UI thread to stay responsive and show progress (the chip moving across the top) during the computer’s turn.

Align 4 v3 screenshot

Hey, you reached the end!

Feel free to check out my other blog posts or subscribe to my RSS feed. You can also click a tag below to see related blog posts.