Your problem is that you are trying to take an event based API and use it as a Promise based one, but events may fire multiple times, while Promise should resolve only once.
The communication between the Worker and the main thread works by sending and receiving messages, but there is by default no one-to-one relation between these messages. Both ends of the communication (ports) will simply stack incoming messages, and handle them sequentially, when they'll get time.
In your code, the main thread's worker.onmessage
handler of f1
has been overwritten by the second call f2
synchronously (one microtask later, but that's still synchronous for our matter).
You could attach your event using the addEventListener
method, at least this way it wouldn't be overwritten. But even then, when the first message event will fire on worker
, both handlers will think it's there own message that did arrive, while in fact it was the one of f2
. so that's not what you need...
What you need is to set up a protocol of communication which would allow both ends to identify each task. You could for instance wrap all your tasks' data with an object containing a .UIID
member, be sure both ends wraps their message this way, and then from main thread check that UUID to resolve the appropriate Promise.
But that can become a bit complicated to implement and to use.
My personal favorite way is to create a new MessageChannel per task. If you don't know this API, I invite you to read this answer of mine explaining the basics.
Since we are sure the only one message that will come through this MessageChannel is the response from the Worker to the one task we sent to it, we can await it just like a Promise.
All we have to do, is to make sure that in the Worker thread we respond through the transferred port instead of the global scope.
const url = getWorkerURL();
const worker = new Worker(url)
const workerFunc = (op) => {
// we create a new MessageChannel
const channel = new MessageChannel();
// we transfer one of its ports to the Worker thread
worker.postMessage(op, [channel.port1]);
return new Promise((res,rej) => {
// we listen for a message from the remaining port of our MessageChannel
channel.port2.onmessage = (evt) => res(evt.data);
});
}
(async () => {
const f1 = await workerFunc("+1");
console.log("f1", f1);
})();
(async () => {
const f2 = await workerFunc("+2");
console.log("f2", f2);
})()
// SO only
function getWorkerURL() {
const elem = document.querySelector( '[type="worker-script"]' );
const script = elem.textContent;
const blob = new Blob( [script], { type: "text/javascript" } );
return URL.createObjectURL( blob );
}
<script type="worker-script">
let a = 0;
const worker = self;
worker.onmessage = (evt) => {
const port = evt.ports[0]; // this is where we will respond
if (evt.data === "+1") {
setTimeout(() => {
a = a + 1;
// we respond through the 'port'
port.postMessage(a);
}, 3000);
}
else if (evt.data === "+2") {
setTimeout(() => {
a = a + 2;
// we respond through the 'port'
port.postMessage(a);
}, 1000)
}
};
</script>