async function() {
while (true) {
await null;
}
}
This will block the thread forever, because the event loop would try to finish all microtasks in one frame.
(async () => {
for (let i = 0; i < 999999; i++) {
await null;
document.body.appendChild(document.createElement("span"));
console.log("running")
}
console.log("finished")
})();
With timeout be like:
- Event loop suspends processing next microtask after 1 sec.
- The engine use the thread to process other code, for example, animation frame.
- Then in the next frame, event loop continues to process microtasks from last frame.
So the while (true)
will block the thread only for 1 sec per frame, because await null;
is suspendable.
Polyfill:
yield
as await
const tasks = new Array;
const resolvedValues = new Array;
const runnables = new WeakSet;
function process(task, resolved) {
try {
const result = task.next(resolved);
if (result.done) {
return null;
}
const { value } = result;
if (value === undefined) {
tasks.unshift(task);
resolvedValues.unshift(undefined);
return;
}
Promise.resolve(value).then((value) => {
tasks.unshift(task);
resolvedValues.unshift(value);
if (unsubscribed) {
unsubscribed = false;
requestAnimationFrame(listener)
}
})
} catch (e) {
console.error(e)
}
return null;
}
let unsubscribed = true;
const listener = function() {
const ms = Date.now();
while (Date.now() - ms < 100) {
if (process(tasks.pop(), resolvedValues.pop()) === null) {
break;
}
}
if (tasks.length > 0) {
requestAnimationFrame(listener);
return;
}
unsubscribed = true
};
function suspend(runnable) {
const task = runnable();
if (runnables.has(task)) {
return;
}
runnables.add(task);
if (process(task) === null) {
return;
}
if (unsubscribed) {
unsubscribed = false;
requestAnimationFrame(listener)
}
}
export { suspend };
let n = 0;
suspend(function*() {
while (true) {
yield;
n++;
console.log("running")
}
});
const resetCount = () => {
console.log(n);
n = 0;
requestAnimationFrame(resetCount);
};
requestAnimationFrame(resetCount);
suspend(function*() {
for (let i = 0; i < 999999; i++) {
yield;
document.body.appendChild(document.createElement("span"));
console.log("running")
}
console.log("finished")
});
"that might break things", I agree, but what might be broken exactly?
I don't mean I must have this feature. I'd like to know why event loop was not designed like that, before microtasks/promises started existing, at that moment no backwards compatibility was needed.