Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active June 8, 2022 04:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dfkaye/8e10996623246d4b178f1d4a7939673d to your computer and use it in GitHub Desktop.
Save dfkaye/8e10996623246d4b178f1d4a7939673d to your computer and use it in GitHub Desktop.
communicate with workers through message channels
// 7 June 2022
// communicate with workers through message channels
// Note: The following example won't run in browser pages with Content Security Policy
// that contains a worker-src directive that does not specify blob: as a value,
// such as a console in a GitHub gist (where I do lot of experimenting).
// If there is no worker-src attribute, then this should *just work*...
// The MessageChannel API lets us send messages between brower windows, tabs,
// iframes, and workers.
// We can set up an embedded worker to initialize itself with a message channel
// by sending a run-once message containing the channel and its receiving port
// as a transferable object.
// A transferable object is no longer accessible to the sender once it is sent
// and accepted by a receiver.
// We can still send messages to workers using the postMessage method, but we
// can now also send them by a message channel's port's postMessage method.
// I am:
// 1. skeptical that using both communication methods at once as it duplicates
// work.
// 2. skeptical of the benefits of communicating by a message channel versus
// communicating directly with a worker.
// 3. open to persuasion of the benefits of either of these doubts.
var script = `
// A local response value.
var response = "Worker says:";
// Handle message as a request.
self.onmessage = function (request) {
if (request.data == 'init') {
// Here's one reason *not* to use both worker.onmessage and port.onmessage.
// This handler has to ignore the initialization command meant for the channel's
// port setup.
console.log("**ignoring init request in worker**");
return
}
var value = request.data;
var type = typeof value;
// Send response.
postMessage([response, "type of ", value, "is", type])
}
// Listen for messages on MessageChannels
self.port2;
self.port2Message = "Worker received on port2:"
self.addEventListener('message', initPort);
// Setup the transferred port from the ports list.
function initPort(e) {
console.log("**running init port request in worker**");
console.log(e.ports);
// self.port2 = e.ports[0];
Object.defineProperty(self, 'port2', {
/*
* This is more than we need for this example, but this pattern lets us define
* the port as an immutable, but still-deletable property on the worker scope,
* in case we let the worker listen to bad actors by other messaging that try
* to overwrite the port's onmessage handler to send themselves all your private
* data. We'll test that hypothesis more thoroughly in a subsequent example...
*/
value: e.ports[0],
writable: false,
enumerable: true,
configurable: true
});
// Handle messages received on port2
port2.onmessage = onPortMessage;
// Remove this function as a listener so we execute it only once.
self.removeEventListener('message', initPort)
}
// Handle messages received on port2
function onPortMessage(e) {
port2.postMessage([port2Message, e.data]);
}
`;
var blob = new Blob([script], {type: 'text/javascript'});
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
var channel = new MessageChannel();
var port1 = channel.port1;
// Handle messages received on port1.
port1.onmessage = function (e) {
console.warn("Received on port1: " + e.data.join(" "));
};
// Handle messages received from the worker.
worker.onmessage = function(response) {
console.log('Received from worker: ' + response.data.join(" "));
};
// Send command to initialize worker with transferable object (channel.port2).
worker.postMessage('init', [channel.port2]);
// Send messages to the channel.
port1.postMessage("**My Message**");
port1.postMessage(33.33);
port1.postMessage(false);
port1.postMessage(null);
port1.postMessage(undefined);
port1.postMessage(6666666n);
port1.postMessage(/^abc$/gim);
port1.postMessage(new Date());
port1.postMessage(new Map());
// Send messages to the worker.
worker.postMessage("**My Message**");
worker.postMessage(33.33);
worker.postMessage(false);
worker.postMessage(null);
worker.postMessage(undefined);
worker.postMessage(6666666n);
worker.postMessage(/^abc$/gim);
worker.postMessage(new Date());
worker.postMessage(new Map());
// Quite a message trace.
// In Firefox the messages are interleaved, while in Chrome and Edge,
// the worker messages appear first, then the channel messages.
// **ignoring init request in worker**
// **running init port request in worker**
// Array [ "[object MessagePort]" ]
// Received from worker: Worker says: type of **My Message** is string
// Received on port1: Worker received on port2: "**My Message**"
// Received from worker: Worker says: type of 33.33 is number
// Received on port1: Worker received on port2: "33.33"
// Received from worker: Worker says: type of false is boolean
// Received on port1: Worker received on port2: "false"
// Received from worker: Worker says: type of is object
// Received on port1: Worker received on port2: "null"
// Received from worker: Worker says: type of is undefined
// Received on port1: Worker received on port2: "undefined"
// Received from worker: Worker says: type of 6666666 is bigint
// Received on port1: Worker received on port2: "6666666"
// Received from worker: Worker says: type of /^abc$/gim is object
// Received on port1: Worker received on port2: "/^abc$/gim"
// Received from worker: Worker says: type of Tue Jun 07 2022 09:33:50 GMT-0700 (Pacific Daylight Time) is object
// Received on port1: Worker received on port2: "Tue Jun 07 2022 09:33:50 GMT-0700 (Pacific Daylight Time)"
// Received from worker: Worker says: type of [object Map] is object
// Received on port1: Worker received on port2: "[object Map]"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment