javascript 为什么两个setTimeouts不在同一个事件循环中

kiayqfof  于 2023-01-16  发布在  Java
关注(0)|答案(2)|浏览(121)
setTimeout(() => new Promise((r) => {console.log(1);r();}).then(() => console.log('1 mic')))
setTimeout(() => new Promise((r) => {console.log(2);r();}).then(() => console.log('2 mic')))
1
1 mic
2
2 mic

为什么两个setTimeouts不在同一个事件循环中
我在谷歌后没有找到一个可以理解的答案

myzjeezk

myzjeezk1#

正如我在Community Wiki answer中所述,原因很简单,调用setTimeout(fn)将在超时到期后将一个新任务排队以执行回调。
因此,每次调用setTimeout都将自己的任务排队。
但是我必须注意,无论如何,您的测试不会告诉您是否在同一个事件循环迭代中运行了两个回调。
实际上,每次调用 * 回调时,都会在“运行脚本后清除”算法中执行一个微任务检查点。
因此,即使在同一个事件循环迭代中有多个回调被触发的情况下,我们也会在每个回调之间有一个微任务检查点。例如,requestAnimationFrame()回调都在同一个事件循环迭代中被触发,作为事件循环的“更新渲染”步骤的一部分。但即使在那里,微任务也会被触发:

requestAnimationFrame(() => {
  console.log(1); Promise.resolve().then(() => console.log(1, "micro"));
  // Try to include a new task in between
  setTimeout(() => { console.log("a new task"); });
  // block the event loop a bit so we ensure our timer should fire already
  const t1 = performance.now();
  while(performance.now() - t1 < 100) {}
});
requestAnimationFrame(() => {
  console.log(2); Promise.resolve().then(() => console.log(2, "micro"));
});

因此,使用微任务并不是一个很好的方法来检查是否在同一个事件循环迭代中触发了两个回调。要做到这一点,你可以尝试调度一个新的任务,就像我在那个例子中所做的那样。但即使这样也不是万无一失的,因为浏览器确实有相当复杂的任务优先级系统,我们不能确定它们何时会触发不同类型的任务。
支持浏览器的最佳方法是使用仍在开发中的Prioritized task scheduler.postTask方法。这允许我们发布具有最高优先级的任务,这样我们就可以检查两个回调是否确实在同一个事件循环迭代中。在不支持此API的浏览器中,我们不得不求助于使用MessageChannel对象,这应该是发布高优先级任务的最接近的方法:

const postTask = globalThis.scheduler
  ? (cb) => scheduler.postTask(cb, { priority: "user-blocking" })
  : postMessageTask;
  
const test = (fn, label) => {
  return new Promise((res) => {
    let sameIteration = true;
    fn(() => {
      console.log(label, "first callback");
      // If this task is executed before the next callback
      // it means both callbacks weren't executed in the same iteration
      postTask(() => sameIteration = false);
      // block the event-loop a bit between both callbacks
      const t1 = performance.now();
      while(performance.now() - t1 < 100) {}
    });
     fn(() => {
      console.log(label, "second callback");
      console.log({ label, sameIteration });
      res();
    });
  });
};
test(setTimeout, "setTimeout")
.then(() => test(requestAnimationFrame, "requestAnimationFrame") );

// If scheduler.postTask isn't available, use a MessageChannel to post high priority tasks
function postMessageTask(cb) {
  const { port1, port2 } = (postMessageTask.channel ??= new MessageChannel());
  port1.start();
  port1.addEventListener("message", () => cb(), { once: true });
  port2.postMessage("");
}
  • 也就是说,如果调用堆栈为空,这是大多数情况下的情况,仅有的例外之一是以编程方式调度的事件的回调。
i7uq4tfw

i7uq4tfw2#

因为每个定时器回调都有自己的任务计划。

相关问题