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

Updating DOM before blocking code by setTimeout or 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>

但是,如果我通过 setTimeout 将 CPU 密集型代码转移到异步时间线,一切都很好,如以下代码片段所示。

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>

然而,由于我知道 promises 也会将您带到 "sort of" 异步时间轴,所以我希望在不使用 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>

由于这篇完美的文章 (https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/),我至少希望它达到 FF 中预期的 运行,可惜不行。

有什么方法可以用承诺来完成这项工作吗?

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

一般microtask就是要等其他JS到运行,然后才能运行before 将控制权交给非 JS 代码。

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

通常 macrotask 意味着它必须等待所有其他 JS 运行 以及 "event loop to tick" - 即:事件触发。

这也是the difference between setImmediate and nextTick in NodeJS

直接回答你的问题:不。 无法强制浏览器在 microtick 更新中进行 运行 DOM 更新 - 虽然它技术上不禁止它这样做 - 它将是 "bad mannered".

对于长时间 运行ning CPU 绑定操作 - 我可以建议改为 Web Workers 吗?

问题是 promise,即使它异步运行,运行得太早。所以浏览器没有时间更新 DOM。这个问题不是特定于承诺的,我在使用延迟为 0 毫秒的 setTimeout 时看到相同的结果:

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>

其实你要的好像是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>

但此时您可以单独使用 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
requestAnimationFrame(_ => blockFor(2000));
<p id="result"></p>

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

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