Using Web Workers

Oleksii Vasyliev, Railsware

Using Web Workers

Brought to you by Alexey Vasiliev, Railsware

Oleksii Vasyliev

Web Workers

Web Workers

Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of a web application. The advantage of this is that laborious processing can be performed in a separate thread, allowing the main (usually the UI) thread to run without being blocked/slowed down

Worker types

Workers specs

Workers run in a different global context than the current "window".

Workers specs

The following Web APIs are available to workers

Barcode Detection API, Broadcast Channel API, Cache API, Channel Messaging API,Console API, Web Crypto API (Crypto), CustomEvent, Data Store (Firefox only), DOMRequest and DOMCursor, Encoding API (TextEncoder, TextDecoder, etc.), Fetch API, FileReader, FileReaderSync (only works in workers!), FormData, ImageData, IndexedDB, Network Information API, Notifications API, Performance API (including: Performance, PerformanceEntry, PerformanceMeasure, PerformanceMark, PerformanceObserver, PerformanceResourceTiming), Promise, Server-sent events, ServiceWorkerRegistration, URL API (e.g. URL), WebGL with OffscreenCanvas (enabled behind a feature preference setting gfx.offscreencanvas.enabled), WebSocket, XMLHttpRequest.

Workers specs

Workers do NOT have access to:

  • The DOM (it's not thread-safe)
  • The window object
  • The document object
  • The parent object

Dedicated (web) workers

Dedicated workers (main script)

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

worker.addEventListener('message', ({data}) => {
  console.log('Worker said: ', data);
}, false);

worker.postMessage('Hello World'); // Send data to our worker.
        

Dedicated workers (worker script)

self.addEventListener('message', (e) => {
  self.postMessage(e.data);
}, false);

Dedicated workers

Example

Most browsers implement the structured cloning algorithm, which allows you to pass more complex types in/out of Workers such as File, Blob, ArrayBuffer, and JSON objects. However, when passing these types of data using postMessage(), a copy is still made. Therefore, if you're passing a large 50MB file (for example), there's a noticeable overhead in getting that file between the worker and the main thread.

Transferable Objects

With Transferable Objects, data is transferred from one context to another. It is zero-copy, which vastly improves the performance of sending data to a Worker. Think of it as pass-by-reference if you're from the C/C++ world. However, unlike pass-by-reference, the 'version' from the calling context is no longer available once transferred to the new context. For example, when transferring an ArrayBuffer from your main app to Worker, the original ArrayBuffer is cleared and no longer usable. Its contents are (quiet literally) transferred to the Worker context.

Transferable Objects

To use transferrable objects, use a slightly different signature of postMessage():

worker.postMessage(arrayBuffer, [arrayBuffer]);
window.postMessage(arrayBuffer, targetOrigin, [arrayBuffer]);

Transferable Objects

The worker case, the first argument is the data and the second is the list of items that should be transferred. The first argument doesn't have to be an ArrayBuffer by the way. For example, it can be a JSON object:

worker.postMessage({data: int8View, moreData: anotherBuffer},
  [int8View.buffer, anotherBuffer]);

Loading External Scripts

You can load external script files or libraries into a worker with the importScripts() function

importScripts('script1.js');
importScripts('script2.js');

importScripts('script1.js', 'script2.js');
        

Subworkers

Workers have the ability to spawn child workers. However, subworkers come with a few caveats:

Inline Workers

var blob = new Blob([
  "self.addEventListener('message', (e) => { self.postMessage(e.data); }, false);"]);

// Obtain a blob URL reference to our worker 'file'.
var blobURL = window.URL.createObjectURL(blob);

var worker = new Worker(blobURL);
worker.addEventListener('message', ({data}) => {
  console.log('Worker said: ', data);
}, false);
worker.postMessage(); // Start the worker.

Use Cases (example)

Shared workers

Shared workers (main script)

A shared worker is accessible by multiple scripts — even if they are being accessed by different windows, iframes or even workers

var myWorker = new SharedWorker('worker.js');

worker.addEventListener('message', ({data}) => {
console.log('Worker said: ', data);
}, false);
worker.postMessage(); // Start the worker.

Shared workers (worker script)

// Event handler called when a tab tries to connect to this worker.
self.addEventListener('connect', (e) => {
  // Get the MessagePort from the event. This will be the
  // communication channel between SharedWorker and the Tab
  const port = e.ports[0]

  port.addEventListener('message', (evt) => handleWorkerMessages({event: evt}))
  // Required when using addEventListener.
  // Otherwise called implicitly by onmessage setter.
  port.start()
})
websocket shared worker
websocket shared worker
websocket shared worker
shared worker support

Service workers

Service Worker

Service workers essentially act as proxy servers that sit between web applications, the browser, and the network (when available)

service worker
service worker and http
ServiceWorkerLifecycle

Service Worker

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(function(registration) {
    // Registration was successful
    console.log('ServiceWorker registration successful with scope: ',
      registration.scope);
  }, function(err) {
    // registration failed :(
    console.log('ServiceWorker registration failed: ', err);
  });
}

Stale-while-revalidate

Stale-while-revalidate

Cache only

Cache only

Network only

Network only

Cache, falling back to network

Cache, falling back to network

Network falling back to cache

Network falling back to cache

Examples

Conclusion

<Thank You!> Questions?

Contact information

QuestionsSlide