任务队列真的有优先级排序系统吗?

给定以下脚本:

数据-lang="js"数据-隐藏="假"数据-控制台="真"数据-巴贝尔="假">
console.log("start of hard script");
const start = performance.now();
setTimeout(() => console.log('setTimeout'),0);
document.addEventListener("DOMContentLoaded", () => {
  console.log('fired event DOMContentLoaded')
});
document.addEventListener("click" , () => {
  console.log("fired event click")
});
while(start + 1000 > performance.now());
console.log("end of hard script")

我确信我在某处读到用户交互队列将比计时器队列具有更高的优先级。

我想看看优先级是如何定义的,但看到in the specs:

让taskQueue是事件循环的任务队列之一,在 实现定义的方式,所选的约束 任务队列必须至少包含一个可运行的任务。如果没有 这样的任务队列,然后跳到下面的微任务步骤。

如果WHATWG没有定义具体的队列顺序,我想知道实现者运行的标准是什么?他们如何评价排队的重要性?最后,我希望看到一个例子,说明这些队列的顺序,如果可能的话。


解决方案

我想知道实现者运行的标准是什么?他们如何评估队列的重要程度?

这基本上是他们的决定,是根据多年的经验做出的设计选择,这些经验考虑了他们的工具是如何使用的,以及应该优先考虑什么(可能也是常识的很大一部分)。

WHATWG实际上根本没有定义这个任务的优先顺序应该如何实现。它们所做的只是定义各种任务源(甚至不是任务队列),以确保在同一个源中排队的两个任务将以正确的顺序执行。

我们最接近于定义某种优先级的是传入的Prioritized Task SchedulingAPI,它将为我们Web作者提供发布优先级任务的平均值,具有三个优先级:"user-blocking""user-visible""background"

要检查浏览器实际实现了什么,您必须仔细检查它们的源代码并进行彻底检查。
我自己已经在那里呆了几个小时,我所能告诉你的是,如果你想全面了解情况,你最好有动力。
您可能会感兴趣的几点:

  • 所有浏览器根本不公开相同的行为。
  • 在Chrome中,setTimeout()的最小延迟仍为1ms(https://crbug.com/402694)
  • 在Firefox中,因为Chrome的1ms延迟会在一些网页上产生不同的结果,所以他们只为页面加载之前计划的定时器创建了一个特殊的极低优先级任务队列,而在加载页面之前计划的定时器则在普通优先级任务队列中排队。(https://bugzil.la/1270059)
  • 至少在Chrome中,每个任务队列has a "starvation" protection,通过在一段时间(不确定有多少时间)之后,让优先级较低的队列也执行它们的一些任务,从而防止所述队列用自己的任务淹没事件循环。

最后,如果可能的话,我希望看到一个显示这些队列顺序的示例。

如前所述,这相当复杂,因为没有一个订单。

您自己的示例非常是一个很好的测试,它在我的Chrome浏览器中确实正确地显示了UI任务队列比计时器任务队列具有更高的优先级(While循环负责我所说的1ms的最小延迟)。但是,对于DOMContent Loaded,我必须承认我不能完全确定它显示了什么重要的东西:HTML解析器也被While循环阻塞,因此只有在整个脚本执行之后才会发布触发事件的任务。

但鉴于此任务发布在DOM Manipulation task source上,我们可以通过强制使用此任务源的其他任务(例如script.onerror)来检查其优先级。 下面是您的代码片段的更新,还有几个任务源,它们的调用顺序与我的Chrome的优先级顺序相反:

数据-lang="js"数据-隐藏="真"数据-控制台="真"数据-巴贝尔="假">
const queueOnDOMManipulationTaskSource = (cb) => {
  const script = document.createElement("script");
  script.onerror = (evt) => {
    script.remove();
    cb();
  };
  script.src = "";
  document.head.append(script);
};
const queueOnTimerTaskSource = (cb) => {
  setTimeout(cb, 0);
}
const queueOnMessageTaskSource = (cb) => {
  const { port1, port2 } = new MessageChannel();
  port1.onmessage = (evt) => {
    port1.close();
    cb();
  };
  port2.postMessage("");
};
const queueOnHistoryTraversalTaskSource = (cb) => {
  history.pushState("", "", location.href);
  addEventListener("popstate", (evt) => {
    cb();
  }, { once: true });
  history.back();
}
const queueOnNetworkingTaskSource = (cb) => {
  const link = document.createElement("link");
  link.onerror = (evt) => {
    link.remove();
    cb();
  };
  link.href = ".foo";
  link.rel = "stylesheet";
  document.head.append(link);
};
const makeCB = (log) => () => console.log(log);
console.log("The page will freeze for 3 seconds, try to click on this frame to queue an UI task");
// let the message show 
setTimeout(() => {
  window.scheduler?.postTask(makeCB("queueTask background"), {
    priority: "background"
  });
  queueOnHistoryTraversalTaskSource(makeCB("History Traversal"));
  queueOnNetworkingTaskSource(makeCB("Networking"));
  queueOnTimerTaskSource(makeCB("Timer"));
  // the next three are a tie in current Chrome
  queueOnMessageTaskSource(makeCB("Message"));
  window.scheduler?.postTask(makeCB("queueTask user-visible"), {
    priority: "user-visible"
  });
  queueOnDOMManipulationTaskSource(makeCB("DOM Manipulation"));

  window.scheduler?.postTask(makeCB("queueTask user-blocking with delay"), {
    priority: "user-blocking",
    delay: 1
  });
  window.scheduler?.postTask(makeCB("queueTask user-blocking"), {
    priority: "user-blocking"
  });
  document.addEventListener("click", makeCB("UI task source"), {
    once: true
  });
  const start = performance.now();
  while (start + 3000 > performance.now());
}, 1000);

相关文章