The TL;DR is that when the onmessage() handler for a web worker is defined as a function declaration as opposed to a function expression, it doesn't work. (At least in Chrome.) And I can't for the life of me think of why.
I have the following dirt-simple example setup:
index.html
<!DOCTYPE html>
<html>
<head>
<title>Web worker test</title>
<script src="index.js"></script>
</head>
<body>
<input type="button" value="test1" onclick="run('test1.js')">
<input type="button" value="test2" onclick="run('test2.js')">
</body>
</html>
index.js
function run(filename) {
worker = new Worker(filename);
worker.postMessage("test");
}
test1.js
onmessage = function(msg) {
console.log(msg.data);
}
console.log(self.onmessage);
test2.js
function onmessage(msg) {
console.log(msg.data);
}
console.log(self.onmessage);
You can try this yourself at http://mrbean.cryogenia.com/~davedude/webworkertest/ . If you click the test1 button, it logs the following (on Chrome 79.0.3945.117 at least):
ƒ (msg) {
console.log(msg.data);
}
test1.js:2 test
This shows that the function is defined as expected, and the handler is working.
In contrast, when you click test2, all you get is this:
ƒ onmessage(msg) {
console.log(msg.data);
}
This shows the script is working, and the function is defined, it's just not functioning as a handler for whatever reason.
The global object's onmessage
property is a setter. Setters do not get invoked by function declarations; rather, they simply get overwritten, as if delete <fnName>
was run beforehand. Here's another example:
<script>
Object.defineProperty(window, 'fn', {
set(newVal) {
console.log('setter invoked');
},
configurable: true
});
</script>
<script>
function fn() {
}
console.log(window.fn);
</script>
As you can see, the setter does not get invoked. The state of the fn
property on window
after the first <script>
is nearly the same as the state of the message
property on self
in the web worker at the start of the web worker's script:
const workerFn = () => {
console.log(Object.getOwnPropertyDescriptor(self, 'onmessage'));
};
const workerFnStr = `(${workerFn})();`;
const blob = new Blob([workerFnStr], { type: 'text/javascript' });
const worker = new Worker(window.URL.createObjectURL(blob));
<div>Look at results in your browser console, not the snippet console</div>
<br><br>
<img src="https://i.sstatic.net/8JND8.png">
The functionality of setting the function as a listener for the message
event will only occur inside the setter. But without the setter being invoked, the listener will not be attached.
If you're curious, in the specification, when a function declaration on the top level exists, and that name already exists on the global object, that name will not be re-created or re-initialized until, at the very end:
Perform !
varEnvRec.SetMutableBinding(fn, fo, false)
.
This will throw an error only if the binding is immutable (eg, if the property is not configurable). But the onmessage
getter/setter is configurable.