@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