@soyguijarro

The multithreaded web

A tale of workers

Creative web developer with an interest in design, communication and data. JavaScript and React enthusiast. Silly pet projects expert.

Ramón Guijarro

The case for workers

A decade-long story

A change in users expectations

Quick load

Fast response

Smooth animation

Offline & Sync

System integration

A lot more

From web pages
to web apps

Quick load

Fast response

Smooth animation

Offline & Sync

System integration

A lot more

Quick load

Fast response

Smooth animation

Offline & Sync

System integration

A lot more

How workers help

Fast response

Smooth animation

Pixel pipeline

Fast response

Smooth animation

Offline & Sync

System integration

Pixel pipeline

Background tasks

Pixel pipeline

JavaScript

Style

Layout

Paint

Composite

60 fps

JavaScript

Style

Layout

Paint

Composite

16 ms

JavaScript execution

JavaScript execution

Styles & layout operations

JavaScript execution

Styles & layout operations

Divide in small batches

Divide in small batches

Run in another thread

Divide in small batches

Run in another thread

Web Workers

Background tasks

Resource caching

App installation

Push notifications

Background sync

Resource caching

App installation

Push notifications

Background sync

Service Workers

Types of workers

More than you
probably expected

Web workers

Audio workers

Service workers

Shared workers

Web workers

Audio workers

Service workers

Shared workers

Web workers

Audio workers

Service workers

Shared workers

Web workers

Audio workers

Service workers

Shared workers

Web workers

Audio workers

Service workers

Shared workers

Web workers vs.
service workers

WW

SW

WW

Common features

Exchange messages

Import other scripts

Do network requests

Cache data & resources

Main code
Worker code

Web workers

Create a worker

const worker = new Worker('worker.js');

Communicate a worker with the main thread

worker.postMessage('Hello!');
self.onmessage = ({ data }) => {
  console.log(data); // Hello!
  self.postMessage('Hello back to you!');
  self.close();
};
self.onmessage = ({ data }) => {
  console.log(data); // Hello!
  self.postMessage('Hello back to you!');
  self.close();
};
worker.onmessage = ({ data }) => {
  console.log(data); // Hello back to you!
};

Import scripts
from a worker

self.importScripts('foo.js', 'bar.js');
self.importScripts('foo.js', 'bar.js');

Service workers

Create a service worker

  navigator.serviceWorker.register('/sw.js');
window.addEventListener('load', () => {

});
  navigator.serviceWorker.register('/sw.js');

self.addEventListener('install', (event) => {
  event.waitUntil(task());
});
self.addEventListener('install', (event) => {
  event.waitUntil(task());
});
self.addEventListener('activate', (event) => {
  event.waitUntil(task());
});
self.addEventListener('fetch', (event) => {
  event.respondWith(getResource(event.request));
});
self.addEventListener('fetch', (event) => {
  event.respondWith(getResource(event.request));
});
self.addEventListener('message', (e) => {...});
self.addEventListener('push', (e) => {...});

Cache network resources

caches.open(CACHE_NAME)
  .then(cache => cache.add('./styles.css));
caches.open(CACHE_NAME)
  .then(cache => cache.addAll([...]));
caches.open(CACHE_NAME)
  .then(cache => cache.put(request, res));
caches.open(CACHE_NAME)
...
...

Get resources from cache

caches.match(request).then((cachedRes) => {
  if (cachedRes) return cachedRes;

  const req = request.clone();
  return fetch(req).then((res) => {
    if (res && res.status === 200) {
      caches.open(CACHE_NAME).then(cache =>
        cache.put(req, res.clone())
      );
    }
    return res;
  });
});
caches.match(request).then((cachedRes) => {
  if (cachedRes) return cachedRes;

  const req = request.clone();
  return fetch(req).then((res) => {
    if (res && res.status === 200) {
      caches.open(CACHE_NAME).then(cache =>
        cache.put(req, res.clone())
      );
    }
    return res;
  });
});
caches.match(request).then((cachedRes) => {
  if (cachedRes) return cachedRes;

  const req = request.clone();
  return fetch(req).then((res) => {
    if (res && res.status === 200) {
      caches.open(CACHE_NAME).then(cache =>
        cache.put(req, res.clone())
      );
    }
    return res;
  });
});
caches.match(request).then((cachedRes) => {
  if (cachedRes) return cachedRes;

  const req = request.clone();
  return fetch(req).then((res) => {
    if (res && res.status === 200) {
      caches.open(CACHE_NAME).then(cache =>
        cache.put(req, res.clone())
      );
    }
    return res;
  });
});
caches.match(request).then((cachedRes) => {
  if (cachedRes) return cachedRes;

  const req = request.clone();
  return fetch(req).then((res) => {
    if (res && res.status === 200) {
      caches.open(CACHE_NAME).then(cache =>
        cache.put(req, res.clone())
      );
    }
    return res;
  });
});
caches.match(request).then((cachedRes) => {
  if (cachedRes) return cachedRes;

  const req = request.clone();
  return fetch(req).then((res) => {
    if (res && res.status === 200) {
      caches.open(CACHE_NAME).then(cache =>
        cache.put(req, res.clone())
      );
    }
    return res;
  });
});
caches.match(request).then((cachedRes) => {
  if (cachedRes) return cachedRes;

  const req = request.clone();
  return fetch(req).then((res) => {
    if (res && res.status === 200) {
      caches.open(CACHE_NAME).then(cache =>
        cache.put(req, res.clone())
      );
    }
    return res;
  });
});

Handle notifications

self.registration.showNotification('Hello', {
  icon: './favicon.png',
  body: 'I am yet another annoying notification',
  actions: [
    { action: 'yes', title: 'Yes' },
    { action: 'no', title: 'No' },
  ],
});
self.addEventListener('notificationclick',
  (event) => {
    console.log(`Clicked ${event.action}`);
    event.notification.close();
  }
);
self.addEventListener('notificationclick',
  (event) => {
    console.log(`Clicked ${event.action}`);
    event.notification.close();
  }
);
self.addEventListener('notificationclose',
  () => {...}
);

Synchronize in the background

navigator.serviceWorker.ready
  .then(registration =>
    registration.sync.register('id')
  );
self.addEventListener('sync', (event) => {
  if (event.tag === 'id') {
    event.waitUntil(runTask());
  }
});

Practical notes

Libraries

promise-worker
work-wrap
promise-worker
work-wrap
sw-precache
sw-toolbox
workbox-sw

Tools

worker-loader
redux-worker-middleware
worker-loader
redux-worker-middleware
offline-plugin

Browser support

Web workers

4

Jan 10

3.5

Jun 09

11.5

Jun 11

4

Jun 09

12

Jun 15

10

Sep 12

~95%

Service workers

~70%

45

Sep 15

44

Jan 16

32

Sep 15

45

Sep 15

44

Jan 16

Service workers

~70%

32

Sep 15

45

Sep 15

44

Jan 16

32

Sep 15

Service workers

~70%

45

Sep 15

44

Jan 16

Service workers

~70%

32

Sep 15

What's next

New frontend architectures

Platform integration features

The web is the future.

 

The future is now.

The web is the future.

Thank you

@soyguijarro

The multithreaded web: a tale of workers

By Ramón Guijarro

The multithreaded web: a tale of workers

  • 1,484