The JavaScript engine
Web Workers
Learn how to use Web Workers to run JavaScript in a background thread, communicating with the main page through postMessage so heavy work won't block the UI.
A Web Worker runs JavaScript in a background thread, separate from the page’s main thread.
A long computation on the main thread freezes the page. A worker gives it its own thread.
The price is isolation. A worker can’t touch window or document. The two sides talk only through messages.
This lesson covers dedicated workers, which belong to one page. The MDN Web Workers guide also covers shared workers. The limits:
- no direct access to the DOM
- the worker script normally needs to be same-origin
- the page and worker don’t share JavaScript objects
file://pages behave inconsistently, so use a local web server
The global scope inside a worker is a WorkerGlobalScope, not Window. That’s why worker code says self.
Browser support
Every current browser has them. To check anyway:
if ('Worker' in window) {
// Web Workers are available
}
Create a Web Worker
Pass the script URL to the Worker constructor:
const worker = new Worker('worker.js')
For import statements inside the worker, create a module worker:
const worker = new Worker('worker.js', {
type: 'module'
})
The MDN Worker constructor reference explains the URL rules.
Communicate with the worker
Two ways:
postMessage()on theWorkerobject- the Channel Messaging API
Using postMessage
The page sends, the worker receives in onmessage:
main.js
const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.js
self.onmessage = event => {
console.log(event.data)
}
self.onerror = event => {
console.error(event.message)
}
The data is copied with the structured clone algorithm, not shared. An ArrayBuffer can be transferred instead, after which the sender can’t use it.
Send messages back
The worker replies with its global postMessage():
worker.js
self.onmessage = event => {
console.log(event.data)
self.postMessage('hey')
}
self.onerror = event => {
console.error(event.message)
}
The page listens with worker.onmessage:
main.js
const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.onmessage = event => {
console.log(event.data)
}
Multiple event listeners
onmessage holds one function. For more, use addEventListener(), same for error:
worker.js
self.addEventListener('message', event => {
console.log(event.data)
self.postMessage('hey')
})
self.addEventListener('message', () => {
console.log(`I'm curious and I'm listening too`)
})
self.addEventListener('error', event => {
console.log(event.message)
})
main.js
const worker = new Worker('worker.js')
worker.postMessage('hello')
worker.addEventListener('message', event => {
console.log(event.data)
})
Using the Channel Messaging API
The Channel Messaging API gives you a channel with two ports. Create it in the page and hand one port to the worker:
main.js
const worker = new Worker('worker.js')
const channel = new MessageChannel()
channel.port1.addEventListener('message', event => {
console.log(event.data)
})
channel.port1.start()
worker.postMessage(
{ port: channel.port2 },
[channel.port2]
)
worker.js
self.addEventListener('message', event => {
const port = event.data.port
port.postMessage('hello from the worker')
})
Notice the second argument to postMessage(): a MessagePort is transferable, so ownership moves to the worker.
Web Worker lifecycle
A worker keeps running after its script finishes, waiting for messages. Stop it from the page with terminate():
main.js
const worker = new Worker('worker.js')
worker.terminate()
It stops immediately, with no time to finish. From inside, call close():
self.onmessage = event => {
console.log(event.data)
self.close()
}
The HTML spec covers worker lifetime and termination.
Loading libraries in a Web Worker
A classic worker loads scripts synchronously with importScripts():
importScripts('../utils/file.js', './something.js')
A module worker uses normal imports instead:
import { calculate } from './calculate.js'
See the MDN importScripts() reference for the cross-origin rules.
APIs available in Web Workers
No DOM, but you still get a lot:
- the Fetch API
- IndexedDB
- Promises
- the Channel Messaging API
- the Cache API
- the Console API
- timers such as
setTimeout()andsetInterval() - WebSockets
cryptoOffscreenCanvas
Check MDN’s list of functions and classes available to workers before relying on one.
Lesson completed