JS Tip of the Day: Promises Block Rendering

Promises Block Rendering
Version: ES2015
Level: Intermediate

Rendering in browsers and the execution of JavaScript occur in the same thread. This means in order for a render to be allowed to happen, any JavaScript that is currently in progress must first run to completion. This is why you may not see any updates on the screen if your JavaScript gets caught in an infinite loop.

// DO NOT RUN!  LOCKS UP BROWSER!
while (true) {
    // browser can't render
}

Promises are used to help manage asynchronous operations, operations like fetching data from a URL which can run in the background without blocking the browser so that the browser can keep rendering while data is being downloaded. However, promises themselves are still capable of locking up the browser because the process that manages their callbacks does not release control to the browser (allowing rendering) despite promises, themselves, being inherently asynchronous.

// DO NOT RUN!  LOCKS UP BROWSER!
function loop () {
    // browser can't render
    return Promise.resolve()
        .then(loop);
}
loop();

The reason for this is because promises are managed in what is known as the microtask queue. Microtasks are tasks that get queued up and handled all within the same iteration of the main event loop that drives everything else, like browser rendering. If a promise is already resolved and has a then() callback, that callback is queued in the microtask queue and then run before the browser is given another chance to render. This will continue to happen until there are no more microtasks to process.

There is no builtin within the ECMAScript spec that allows you to escape the microtask queue or delay execution in a way that would allow the browser to render (the main event loop is not part of the ECMAScript spec). The only way to do this would be through something else provided by the Web API such as setTimeout() or requestAnimationFrame().

// Safe in browser
function loop () {
    return Promise.resolve()
        .then(() => requestAnimationFrame(loop)); // allows renders
}
loop();

More info: