I am trying to write a web worker that performs an interruptible computation. The only way to do that (other than Worker.terminate()
) that I know is to periodically yield to the message loop so it can check if there are any new messages. For example this web worker calculates the sum of the integers from 0 to data
, but if you send it a new message while the calculation is in progress it will cancel the calculation and start a new one.
let currentTask = {
cancelled: false,
}
onmessage = event => {
// Cancel the current task if there is one.
currentTask.cancelled = true;
// Make a new task (this takes advantage of objects being references in Javascript).
currentTask = {
cancelled: false,
};
performComputation(currentTask, event.data);
}
// Wait for setTimeout(0) to complete, so that the event loop can receive any pending messages.
function yieldToMacrotasks() {
return new Promise((resolve) => setTimeout(resolve));
}
async function performComputation(task, data) {
let total = 0;
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
// Yield to the event loop.
await yieldToMacrotasks();
// Check if this task has been superceded by another one.
if (task.cancelled) {
return;
}
}
// Return the result.
postMessage(total);
}
This works but it is appallingly slow. On average each iteration of the while
loop takes 4 ms on my machine! That is a pretty huge overhead if you want cancellation to happen quickly.
Why is this so slow? And is there a faster way to do this?