Web Workers

Beyond The Wall

"Native" feeling apps

"Native" = "Not Web"

60fps

16ms

Hardware acceleration

translateZ(0)

will-change

In particular, never use the main thread to perform long-running or potentially unbounded tasks, such as tasks that require network access. Instead, always move those tasks onto background threads. —Apple Developer Guide
Because of the single thread model described above, it's vital to the responsiveness of your application's UI that you do not block the UI thread. —Android Developer Guide

In the Web Platform, the Main thread is also the UI thread.

Web Workers

New!

New?

Worker

var w = new Worker(url)

w.postMessage(data)
w.addEventListener('message', handler)
function handler(e) { /*e.data*/ }

No Shared State

NO DOM!

What can you do?

DedicatedWorkerGlobalScope

like window, for Workers

postMessage(data)

addEventListener('message')

XMLHttpRequest

(fetch)

WebSocket

IndexedDB

importScripts()

<script></script>
<script></script>
<script></script>

importScripts()

<script></script>
<script></script>
<script></script>

importScripts()

<script></script>
<script></script>
<script></script>

importScripts()

<script></script>
<script></script>
<script></script>

Message Passing

postMessage

Not quite the same as window.postMessage

No Shared State

Serialization

postMessage(JSON.stringify(obj));

JSON

Date

Structured Clone Algorithm

Function

// fibworker.js
addEventListener('message', function (e) {
  var n = e.data;
  var a = 0, b = 1, f = 1;
    for(var i = 2; i <= n; i++) {
        f = a + b;
        a = b;
        b = f;
    }
    postMessage(f);
});
// app.js
var fibonacciWorker = new Worker('fibworker.js');
fibonacciWorker.addEventListener('message', function (e) {
  console.log(e.data); // result
});
fibonacciWorker.postMessage(20); // request

Remote Procedure Call

// interviewWorker.js

addEventListener('message', function (e) {
  var message = e.data;
  if (message.method === 'fibonacci') {
    postMessage(fibonacci(message.input));
  }
  if (message.method === 'fizzbuzz') {
    postMessage(fizzbuzz(message.input));
  }
});

function fibonacci (n) {
  /* No Spoilers!! */
}

function fizzbuzz (n) {
  /* No Spoilers!! */
}
var w = new Worker('interviewWorker.js');

if (question === 'fizzbuzz') {
  w.postMessage({ method: 'fizzbuzz', input: input });
}

if (question === 'fibonacci') {
  w.postMessage({ method: 'fibonacci', input: input });
}

HIRED

Promises!

// app.js
var remoteFibonacci = (function () {
  var worker = new Worker('fibworker.js');
  var callId = 0;
  return function remoteFibonacci (n) {
    var cId = callId;
    return new Promise(function (resolve, reject) {
      w.postMessage({
        input: n,
        callId: cId
      });
      w.addEventListener('message', function (e) {
        if (e.data.callId === cId) {
          resolve(e.data);
        }
      });
    });
    callId++
  };
})();
// fibworker.js
addEventListener('message', function (e) {
  var message = e.data;
  var callId = message.callId;
  var n = message.input;
  var a = 0, b = 1, f = 1;
  for(var i = 2; i <= n; i++) {
      f = a + b;
      a = b;
      b = f;
  }
  postMessage({
    callId: callId,
    result: f
  });
});
remoteFibonacci(20).then(alert);
// fibworker.js
addEventListener('message', function (e) {
  var message = e.data;
  var callId = message.callId;
  var n = message.input;
  var a = 0, b = 1, f = 1;
  for(var i = 2; i <= n; i++) {
      f = a + b;
      a = b;
      b = f;
  }
  setTimeout(function () {
    postMessage({
      callId: callId,
      result: f
    });
  }, Math.random() * 10000);
});

Worker +
RPC +
Promise

Worker +
RPC +
Promise

Worker +
RPC +
Promise

PromiseRPCWorker

PromiseRPCWorkerFactoryBean

PromiseWorker

// fibworker.js
importScripts('promiseWorker.js');

function fibonacci (n) {
  var a = 0, b = 1, f = 1;
  for (var i = 2; i <= n; i++) {
    f = a + b;
    a = b;
    b = f;
  }
  return f;
}

// register comes from promiseWorker.js
register({
  fibonacci: fibonacci
});
promiseWorker('fibworker.js').then(function (api) {
  api.fibonacci(20).then(alert);
});

Refactoring sucks.

function businessLogic() {
  var n = getFoo();
  n = frobulate(n);
  var result = fibonacci(n);
  showPopupWindow();
  return result;
}
function businessLogic(callback) {
var n = getFoo();
frobulate(n)
  .then(fibonacci)
  .then(function (result) {
    showPopupWindow();
    callback(result);
  });
}

ES2016 Async Functions

function businessLogic() {
  var n = getFoo();
  n = frobulate(n);
  var result = fibonacci(n);
  showPopupWindow();
  return result;
}
async function businessLogic() {
  var n = getFoo();
  n = await frobulate(n);
  var result = await fibonacci(n);
  showPopupWindow();
  return result;
}

import {magic} from "future.js";

Any non-UI operation that can be done in a separate thread, should.

Drawbacks

Must be separate file

More HTTP requests = :(
function BlobWorker(fn) {
  var code = fn.toSource();
  var blob = new Blob(
    [code],
    {"type": "text/javascript"}
  );
  var url = URL.createObjectURL(blob);
  return new Worker(url);
}

DO NOT DO THIS
UNLESS YOU WANT TO THEN OKAY

DO NOT DO THIS
UNLESS YOU WANT TO THEN OKAY

Performance overhead

Service Workers!

Service Workers!

Start the multithreading today!

Thanks!

@potch · github.com/potch