Web workers?

Ash Kyd

What's a worker?

A worker runs some code in a separate process.

No DOM or window access, limited API.

Communicates via postMessage API.

Multiprocess Javascript!

Pretty good sandbox

Ideal for discrete tasks

Longer running, CPU intensive

Frees up the render thread

Types of worker

  • Web worker
    • Dedicated worker
    • Shared worker
  • Service worker

Start up a worker

index.js:


var worker = new Worker('/worker.js');

worker.onmessage = function(message){
    console.log('Message from worker: ' + message.data);
};

worker.postMessage('Hello Worker!');
    

Worker internals

worker.js:


addEventListener('message', function(e) {
    // Print the message
    console.log('Message from page: ' + e.data);

    // Send a greeting in reply.
    if(e.data.match(/Hello/)){
        postMessage('Hello back.');
    }
}, false);
    

postMessage API

myWorker.postMessage([mixed] aMessage, [array] transferList);

Modern browsers support Structured Clone.

Duplicates your message to send to the worker.

Transfer ArrayBuffers via transferList.

Let's poke around…

index.html


var echoWorker = new Worker('/scripts/echoworker.js');
echoWorker.onmessage = function(message){
    console.log('%cMessage from worker: ', 'color: darkred', message);
};
        

echoworker.js


addEventListener('message', function(e) {
    console.log('%cMessage from parent: ', 'color: blue', e.data);
    postMessage(e.data);
}, false);
        

Transferrable objects


var selectedFile = document.getElementById('input').files[0];
var reader = new FileReader();
reader.onload = function(e){
    window.arrayBuffer = e.target.result;
    console.log('Loaded file, byteLength: ', arrayBuffer.byteLength);
    console.groupEnd();
};
reader.readAsArrayBuffer(selectedFile);

Worker Scope

  • WorkerGlobalScope.importScripts()
  • WorkerGlobalScope.close()

APIs available

Most of the non-DOM APIs are available.

  • XHR ๐Ÿ‘
  • IndexedDB ๐Ÿ‘
  • WebSockets ๐Ÿ‘
  • et al
  • LocalStorage ๐Ÿ‘Ž

Solving for window

Lots of browser code expects the global to be window

// Map `window` to `self` inside a closure
(function(window){    if(window.indexedDB){
        doStuff();
    }
})(self);

Solving for window


// nuclear option
self.window = self;

How fast is it?

Low-end Firefox OS benchmark (via Mozilla Hacks):

OperationCost
Initialisation~40ms
postMessage latency~0.5ms
Communication speed45kB/ms

Building apps with workers

Opinion: Use fewer, generalist workers.

Limit to CPU cores -1.

navigator.hardwareConcurrency (and polyfills)

Develop a protocol

Workers aren't smart, but you can make smart things.

  • Async โ€œdo somethingโ€ API
  • Run your model in a worker
  • Misc, other!

Readymade implementations

workerproxy gives you some sugar:

var proxyWorker = createWorkerProxy(new Worker('proxyworker.js'));
createWorkerProxy({
    greet: function(name, callback) {
        callback(null, 'Hello, ' + name + '!');
    },
    tokenize: tokenize,
    pluralize: pluralize
}, {autoCallback: true});

Workerify

npm install browserify workerify
browserify -t workerify index.js
var worker = new Worker(window.URL.createObjectURL(
    new Blob(['BROWSERIFIED CONTENTS OF worker.js'])
));

Use with caution

Only useful for dedicated worker.

Inlined files can be difficult to debug.

Can't (easily) uglify the worker code.

Lose out on compiler optimisations.

Case studies are hard

  • Google Apps
  • mega.nz
  • Ace editor
  • Alchemize (shameless plug)

Workers are not something you notice.

Can I use Web Workers?

Yeah. It's at 86% market support.

Can I use Service Workers?

Maybe. It's pretty much just Chrome right now.

Can I use Service Workers?

Though that's still a large audience.

Further reading

  • Shared workers
  • Service worker
  • Broadcast channel - Mozilla proposal

Some resources

Web workers!

Ash Kyd