MessageChannel
Occasionally, you want Web Workers to communicate with each other. Doing so is not obvious as most Web Worker examples are about communicating between the main thread and a Web Worker. There, one uses postMessage()
to send messages directly to the Worker. Alas, that doesn’t work for communicating between two Workers, because you can’t pass references to Workers around.
MessageChannel
The solution is to establish a channel between the Workers:
const channel = new MessageChannel();
receivingWorker.postMessage({port: channel.port1}, [channel.port1]);
sendingWorker.postMessage({port: channel.port2}, [channel.port2]);
We are creating a new MessageChannel
and sending references to its two ports to two Workers. Every port can both send and receive messages. Note the second parameter of postMessage()
: It specifies that channel.port1
and channel.port2
should be transfered (not copied) to the Workers. We can do that because MessagePort
implements the interface Transferable
.
sendingWorker
posts messages and looks as follows:
const sendingWorker = createWorker(() => {
self.addEventListener('message', function (e) { // (A)
const {port} = e.data; // (B)
port.postMessage(['hello', 'world']); // (C)
});
});
The tool function createWorker()
for inlining Worker code is explained later.
The Worker performs the following steps:
port
holds the MessagePort
.port
.receivingWorker
first receives its port and then uses it to receive messages:
const receivingWorker = createWorker(() => {
self.addEventListener('message', function (e) {
const {port} = e.data;
port.onmessage = function (e) { // (A)
console.log(e.data);
};
});
});
In line A, we could also have used port.addEventListener('message', ···)
. But then you need to explicitly call port.start()
before you can receive messages. Unfortunately, the setter is more magical here than the method.
The nice thing about receiving messages is that the channel buffers posted messages. Therefore, there is no need to worry about race conditions (sending too early or listening too late).
The following tool function is used for inlining Web Workers.
function createWorker(workerFunc) {
if (! (workerFunc instanceof Function)) {
throw new Error('Argument must be function');
}
const src = `(${workerFunc})();`;
const blob = new Blob([src], {type: 'application/javascript'});
const url = URL.createObjectURL(blob);
return new Worker(url);
}
For older browsers, using separate source files is safer, because creating Workers from blobs can be buggy and/or unsupported.
MessagePort
” on MDNAcknowledgements: Tips on Twitter from @mourner, @nolanlawson and @Dolphin_Wood helped with this blog post.