MessageChannelOccasionally, 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.