ServiceWorker intro

A screencast of ServiceWorker technology.

A short video about ServiceWorker technology I have prepared.

Extra info

JSAir service worker

  • When the JSAir service worker installs, it caches just the index page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Here comes the install event!
// This only happens once, when the browser sees this
// version of the ServiceWorker for the first time.
self.addEventListener('install', function onServiceWorkerInstall(event) {
console.log('install event', event)
// We pass a promise to event.waitUntil to signal how
// long install takes, and if it failed
event.waitUntil(
// We open a cache
caches.open(currentCache).then(function addResourceToCache(cache) {
return cache.addAll([
'/',
])
})
)
})
  • When the home page sends a request for a static resource it is intercepted by the service worker.
1
2
3
4
5
6
7
8
9
10
11
12
// The fetch event happens for the page request with the
// ServiceWorker's scope, and any request made within that
// page
self.addEventListener('fetch', function onServiceWorkerFetch(event) {
console.log('fetch event', event)
// Calling event.respondWith means we're in charge
// of providing the response. We pass in a promise
// that resolves with a response object
event.respondWith(
...
)
})
  • The fetch event handling first tries to call the network and actually fetch the requested resource.
1
2
3
4
5
6
7
8
9
event.respondWith(
fetch(event.request).then(function updateCacheAndReturnNetworkResponse(networkResponse) {
console.log(`fetch from network for ${event.request.url} successfull, updating cache`)
caches.open(currentCache).then(function addToCache(cache) {
return cache.add(event.request)
})
return networkResponse
})
)

Note how the fetched result is added to the cache on success.

  • If the network is unavailable (offline mode) or the server is not responding for any reason, the fetch promise will be rejected and the service worker tries to lookup the requested resource in the cache.
1
2
3
4
5
6
7
8
9
fetch(event.request).then(function updateCacheAndReturnNetworkResponse(networkResponse) {
...
}).catch(function lookupCachedResponse(reason) {
// On failure, look up in the Cache for the requested resource
console.log(`fetch from network for ${event.request.url} failed:`, reason)
return caches.match(event.request).then(function returnCachedResponse(cachedResponse) {
return cachedResponse
})
})

Hopefully in offline mode the requested resource has been cached already. Otherwise the caches.match(event.request) promise is rejected too and the index page receives 404 response automatically.

  • Waiting for the network request to fail leads to 300ms delay before the cached resource is returned. You can see this by turning the WiFi off and reloading the JSAir page with the Network DevTools tab open.

Misc (not in the screencast)

  • Zeit.co service worker is created using webpack.
  • This service worker tries cache-first policy for the intercepted resources
1
2
3
4
5
6
7
8
self.addEventListener('fetch', function (event) {
event.respondWith(self.caches.open(CACHE_NAME).then(function (cache) {
return cache.match(event.request).then(function (response) {
if (response) return response;
return self.fetch(event.request);
});
}));
});

First, it will look up the resource in the cache, and if it unavailable will make the network request.

  • Note the network response is NOT put into the cache. Instead of caching every received response, the Zeit.co index page explicitly sends a message to the service worker, asking to prefetch a specific resource.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
self.addEventListener('message', function (event) {
self.caches.open(CACHE_NAME).then(function (cache) {
(function () {
switch (event.data.command) {
case 'add':
var request = new self.Request(event.data.url, { mode: 'no-cors' });
self.fetch(request).then(function (res) {
cache.put(request, res).then(function () {
event.ports[0].postMessage({ error: null });
});
});
break;
default:
throw new Error('Unknown prefetcher command ' + event.data.command);
}
})();
});
});