Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Starving the Event Loop with Microtasks

Starving the Event Loop with microtasks

"What's the Event Loop?"

Sparked from this twitter conversation when talking about doing fast async rendering of declarative UIs in Preact

These examples show how it's possible to starve the main event loop with microtasks (because the microtask queue is emptied at the end of every item in the event loop queue). Note that these are contrived examples, but can be reflective of situations where Promises are incorrectly expected to yield to the event loop "because they're async".

  • setTimeout-only.js is there to form a baseline
  • chained-promises.js shows the event loop being starved when many microtasks are queued at once
  • promises-returning-promises.js shows chained .then() calls starving the event loop
  • nested-chained-promises.js shows nested .then() calls starving the event loop
  • promise-all.js shows Promise.all() also suffers from this

For more on microtasks and the event loop, check out Jake Archibald's article "Tasks, microtasks, queues and schedules"

let start;
setTimeout((_=>console.log(performance.now() - start)), 0)
let p = Promise.resolve(1)
for(let i = 100000; i; i--) {
p = p.then(_ => 1);
}
start = performance.now();
// Invokes overhead of creating new Promise objects until the start of each microtask.
let start;
setTimeout((_=>console.log(performance.now() - start)), 0)
let count = 100000
function createNewPromise() {
return Promise.resolve(1).then(_ => {
if (count) {
createNewPromise()
count--;
}
})
}
Promise.resolve(1).then(createNewPromise);
start = performance.now();
// Running parallel promises all occur in the single turn of the micro task queue
let start;
setTimeout((_=>console.log(performance.now() - start)), 0)
let promises = []
for(let i = 100000; i; i--) {
promises.push(Promise.resolve(1));
}
Promise.all(promises)
start = performance.now();
// Invokes overhead of creating new Promise objects
let start;
setTimeout((_=>console.log(performance.now() - start)), 0)
let p = Promise.resolve(1);
for(let i = 100000; i; i--) {
p = p.then(_ => Promise.resolve(1));
}
start = performance.now();
let start;
setTimeout((_=>console.log(performance.now() - start)), 0)
start = performance.now();
@richardscarrott

This comment has been minimized.

Copy link

@richardscarrott richardscarrott commented Apr 25, 2020

Should nested-chained-promises.js not return the nested promise on line 8?

@jesstelford

This comment has been minimized.

Copy link
Owner Author

@jesstelford jesstelford commented Apr 26, 2020

It could, but doesn't have to. The goal of that example is only to show that a promise within a promise still gets resolved on the same turn of the mocrotask queue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.