Updating DOM before blocking code by setTimeout or promise

亡梦爱人 提交于 2019-12-07 11:41:35

问题


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?


回答1:


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?




回答2:


The problem is that the promise, even if it runs asynchronously, runs too early. So browsers don't have time to update the DOM. This problem is not specific to promises, I see the same result when using a setTimeout with a 0ms delay:

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
setTimeout(_ => blockFor(2000), 0);      // 0ms is not enough
<p id="result"></p>

In fact, it seems what you want is requestAnimationFrame:

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
new Promise(function(resolve) {
  requestAnimationFrame(_ => resolve(2000));
}).then(blockFor);
<p id="result"></p>

But at this point you could use requestAnimationFrame alone, without promises.

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
requestAnimationFrame(_ => blockFor(2000));
<p id="result"></p>



回答3:


Best way to do it is to delegate the heavy process to a web worker...

// main thread

document.getElementById("result").addEventListener('click', handleClick);
const worker = new Worker('worker.js');


function handleClick(){
  worker.onmessage = e => {
   console.log('main', e.data.response)  
   this.textContent = e.data.response;
  }
  this.textContent = "Please remain...";
  worker.postMessage({data: 2000});
}

// worker

self.addEventListener('message', e => {
    const { data } = e.data;
    console.log('worker', data); 

    function blockFor(dur){
     var now = new Date().getTime();
     while (new Date().getTime() < now + dur);
     }

    blockFor(data)
    self.postMessage({ response: "I am done..!" });
});



  // NOTE: perform this test on your app for browser compatibility
  if (window.Worker) {
  ...

}

Check out this live code

MDN web workers docs



来源:https://stackoverflow.com/questions/39184297/updating-dom-before-blocking-code-by-settimeout-or-promise

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!