A Brief Introduction to Web Workers

Web Workers are essential for performant Javascript applications, but few programmers leverage or fully understand them.

This post serves as a general introduction to Web Workers. In a later post (likely next week), I’ll provide some Ember specific examples.

What are Web Workers?

Web Workers run Javascript scripts in the background, allowing parallel execution. (developer note: Concurrency is not Parellelism).

Spawning a new Worker is as simple as:

  const thisWorker = new Worker("path/to/script.js")

Workers mostly run in new threads, but sometimes spawn a new process (such as the case of Service Workers).

You can view the full Web Workers spec here: http://www.whatwg.org/specs/web-workers/current-work/

There are four types of Workers. Dedicated Workers, Shared Workers, Service Workers & Audio Workers. Dedicated Workers are only accessible by their parent (whomever spawned them), whereas Shared Workers do not have this limitation. Service workers run independent of our app, with a good example being Push Notifications.

Uses for Web Workers

Javascript is single threaded. To date, our big three SPAs are no different (Ember, React, Angular).

This means only one item is executing at a time. This can become problematic when we consider the multitude of tasks running in an SPA: UI Rendering, AJAX requests/callbacks and any framework specific setup / teardown.

Some easy wins with Workers:
  • Backgrounding AJAX requests and callbacks;
  • Large scale data parsing/processing (e.g. posing a users phone book);
  • Localized tasks we don’t want happening on our UI thread (e.g. highlighting text, spell check); and
  • Generating complex html fragments, which can then be injected to our main template.

We find Web Workers particularly useful in a mobile context, especially when building Hybrid Mobile Applications with Ember & Cordova that need to mimic complex native functionality.

Historically, developers would handle these cases with asynchronous callbacks and timeouts/intervals. While this technique is less blocking, it is rarely as performant as spawning a new thread.

Key points when using a Worker

A Worker lives in an isolated environment. It does not have access to the DOM or Window objects (such as Cordova, localStorage and any plugins you have installed). When you think about it, this makes a lot of sense.

You can import other scripts in to a worker, but this is not always the best practice. Consider the overhead of every library you are introducing, particularly when it comes to the workers spawn time and memory footprints.

Workers do not share their memory buffer with the main thread, so there is no net improvement when importing the same script to two different Workers.

You should also consider using a beforeunload hook to tear down Workers before transitioning. There is no guarantee workers will be torn down because you have navigated away from a page, which can lead to somewhat serious memory issues.

Communicating with your main thread

Given workers run in isolation, we need a way for our main thread to communicate with the Worker, and for the Worker to talk back. The two main techniques are MessageChannel & the standard window.postMessage.

Prompted by a post by Nolan Lawson, Isle of Codes' Chris wrote a post benchmarking WebWorker message performance.

His findings were that postMessage is faster than MessageChannel. In addition, for Chrome & Firefox it is fastest to fully stringify your JSON object before passing, whereas Safari/iOS is more performant when passing the non stringified object.

Building an arbitrary worker


"use strict";

 Standard worker handler
onmessage = function(event) {
  var params = JSON.parse(event.data);

  //communicate with our main thread
  postMessage("thanks - I got the data");

var myWorker = new Worker("workers/sample-worker.js");

myWorker.onmessage = function(event) {
  console.log("worker returned " + event.data);

myWorker.postMessage(“Init Message”);

To close the Worker, we could call myWorker.terminate() from app.js or close() within the Worker itself.

Web Worker Availability

Web Workers are available on most major browsers. Within a mobile context, you are safe to assume Web Workers will be available.

Only older versions of IE (8 & 9) and Opera Mini do not have Web Worker Support (http://caniuse.com/#feat=webworkers), however polyfills do exist for IE 8 & 9.