Search code examples
javascriptasynchronouspromisesettimeoutes6-promise

Updating DOM before blocking code by setTimeout or promise


I know that when there is a CPU intensive code any immediate previous DOM update won't happen. Such as

function blockFor(dur){
  var now = new Date().getTime();
  while (new Date().getTime() < now + dur);
  result.textContent = "I am done..!";
}

result.textContent = "Please remain..."; // we will never see this
blockFor(2000);
<p id="result"></p>

However if I shift the CPU intensive code to the asynchronous timeline by setTimeout it's all fine as in the following snippet.

function blockFor(dur){
  var now = new Date().getTime();
  while (new Date().getTime() < now + dur);
  result.textContent = "I am done..!";
}

result.textContent = "Please remain..."; // now you see me
setTimeout(_ => blockFor(2000),15);      // 15ms to be on the safe side
<p id="result"></p>

However since i know that promises also take you to a "sort of" asycnronous timeline i was expecting to achieve the same effect without using the setTimeout hack. Such as;

function blockFor(dur){
  var now = new Date().getTime();
  while (new Date().getTime() < now + dur);
  result.textContent = "I am done..!";
}

result.textContent = "Please remain..."; // not in Chrome not in FF
Promise.resolve(2000)
       .then(blockFor)
<p id="result"></p>

I would at least expect this to run as expected in FF because of this perfect article (https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) alas no way.

Is there any way to accomplish this job with promises?


Solution

  • Promise.prototype.then has microtask semantics. This means it has to wait for synchronous code to run but not for asynchronous code to run - browsers probably choose to wait for all JS to run before doing DOM updates.

    Generally microtask means it has to wait for other JS to run, and then it can run before yielding control to non JS code.

    setTimeout has macrotask semantics. It runs as a part of the DOM API and when the callback runs the non-js code has already gotten a chance to run. Browsers already run their own code when this runs so they also process events and DOM updates.

    Generally macrotask means that it has to wait for all other JS to run and also for the "event loop to tick" - that is: events to fire.

    This is also the difference between setImmediate and nextTick in NodeJS.

    To answer your question directly: no. There is no way to force the browser to run DOM updates in a microtick update - while it is technically not forbidden for it to do so - it would be "bad mannered".

    For long running CPU bound operations - may I suggest Web Workers instead?