@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');

From Service Workers: An Introduction
By Matt Gaunt (Google Developers)

From Service Workers: An Introduction
By Matt Gaunt (Google Developers)

From Service Workers: An Introduction
By Matt Gaunt (Google Developers)
self.addEventListener('install', (event) => {
event.waitUntil(task());
});
self.addEventListener('install', (event) => {
event.waitUntil(task());
});

From Service Workers: An Introduction
By Matt Gaunt (Google Developers)
self.addEventListener('activate', (event) => {
event.waitUntil(task());
});

From Service Workers: An Introduction
By Matt Gaunt (Google Developers)

From Service Workers: An Introduction
By Matt Gaunt (Google Developers)

From Service Workers: An Introduction
By Matt Gaunt (Google Developers)
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,709