在通过setTimeout或promise阻止代码之前更新DOM

时间:2016-08-27 18:15:23

标签: javascript asynchronous promise settimeout es6-promise

我知道当存在CPU密集型代码时,不会发生任何直接的先前DOM更新。如

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>

但是,如果我将CPU密集型代码转移到异步时间轴setTimeout,那么它就像在下面的代码段中一样正常。

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>

然而,因为我知道承诺也会带你到一个“类型”的asycnronous时间线,我期望在不使用setTimeout hack的情况下达到相同的效果。如;

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>

我至少期望这会在FF中按预期运行,因为这篇完美的文章(https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/)唉,没办法。

有没有办法用承诺完成这项工作?

3 个答案:

答案 0 :(得分:3)

Promise.prototype .then具有 microtask 语义。这意味着它必须等待同步代码运行,但不能运行异步代码 - 浏览器可能会选择在进行DOM更新之前等待所有JS运行。

通常微任务意味着它必须等待其他JS运行,然后它才能在控制非JS代码之前运行

setTimeout具有 macrotask 语义。它作为DOM API 的一部分运行,当回调运行时,非js代码已经有机会运行。浏览器在运行时已经运行了自己的代码,因此它们也处理事件和DOM更新。

通常 macrotask 意味着它必须等待所有其他JS运行,并且还要“等待事件循环” - 即:要触发的事件。

这也是the difference between setImmediate and nextTick in NodeJS

直接回答你的问题:不。没有办法强制浏览器在microtick更新中运行DOM更新 - 虽然技术上不禁止它这样做 - 它会是“不礼貌”。

对于长时间运行的CPU绑定操作 - 我可以建议使用Web Workers吗?

答案 1 :(得分:1)

问题在于承诺,即使它以异步方式运行,也会过早运行。因此浏览器没有时间更新DOM。这个问题不是特定于承诺的,当使用setTimeout延迟0ms时,我看到相同的结果:

&#13;
&#13;
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
&#13;
<p id="result"></p>
&#13;
&#13;
&#13;

事实上,你想要的似乎是requestAnimationFrame

&#13;
&#13;
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);
&#13;
<p id="result"></p>
&#13;
&#13;
&#13;

但此时你可以单独使用requestAnimationFrame,没有承诺。

&#13;
&#13;
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));
&#13;
<p id="result"></p>
&#13;
&#13;
&#13;

答案 2 :(得分:0)

最好的方法是将繁重的过程委派给网络工作者...

// 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