事件循环中有哪些类型的队列?

Which types of queues are in event loop?

我在不同的文章中遇到 render queue 的提及 (example, example) 两位作者都说

render callback is given the highest priority

  1. 这是真的吗?
  2. render queue 是否作为单独的队列存在或 它是渲染回调的别名?
  3. 渲染了哪些回调?正如我所接受的任何重绘都是渲染回调
  4. 还有其他类型的队列吗?如果有,我可以从哪里了解它们?

w3.org中我们可以读到

tasks from different task sources may be placed in different task queues

但没有提及类型或优先级

1) render callback is given the highest priority. Is it true?

从我阅读下面的 link 来看,我是这么认为的。

2) Does render queue exist as separate queue or is it alias for render callbacks?

下面的link说的是同一个任务队列,"Event"任务队列

3) Which callbacks are render? As I take in any repaint is render callback

我认为渲染回调基本上是浏览器根据 dom.

的最新更新定期尝试 repaint/render 屏幕的过程

4) Are there any other types of queues and if there are where can I read about them?

是的,请在此处查看详细信息:https://html.spec.whatwg.org/multipage/webappapis.html#task-queue

这似乎是对此的最佳报道:https://html.spec.whatwg.org/multipage/webappapis.html#event-loops

我认为那是不正确的,除非我有误解。这是我目前的信息来源。

"Immediately after every macrotask, the engine executes all tasks from microtask queue, prior to running any other macrotasks or rendering or anything else."

https://javascript.info/event-loop

下面是一个在我的代码片段中没有表现的实验,但它表明来自 .then 内部 button_one() 的微任务优先于按钮上的渲染更改。从"button one out"的控制台输出可以看出微任务在按钮函数之后。


<body>
    <button id="but_one" onclick='button_one()'>Click me</button>
    <button id="but_two" onclick='button_two()'>Reset</button>
</body>
<script>
    function button_one() {
        console.log("button one clicked")
        document.getElementById("but_one").innerText ='YouClickedMe';
        autoResolve().then(msg =>{
            console.log("Inside resolve callback")
            sleep(5000)
        });
        console.log("button one out")
    }

    function button_two() {
        document.getElementById("but_one").innerText = 'Click me';
    }

    function sleep(milliseconds) {
        const date = Date.now();
        let currentDate = null;
        do {
            currentDate = Date.now();
        } while (currentDate - date < milliseconds);
    }

    async function autoResolve() { return "resolved" }
</script>

</html>

根据 HTML 规范,关于此事的最重要的一点是在 task-source definition 中我们可以阅读:

Per its source field, each task is defined as coming from a specific task source. For each event loop, every task source must be associated with a specific task queue.

Note

Essentially, task sources are used within standards to separate logically-different types of tasks, which a user agent might wish to distinguish between. Task queues are used by user agents to coalesce task sources within a given event loop.

Example

For example, a user agent could have one task queue for mouse and key events (to which the user interaction task source is associated), and another to which all other task sources are associated. Then, using the freedom granted in the initial step of the event loop processing model, it could give keyboard and mouse events preference over other tasks three-quarters of the time, keeping the interface responsive but not starving other task queues. Note that in this setup, the processing model still enforces that the user agent would never process events from any one task source out of order.

并且在 task queue definition:

An event loop has one or more task queues. A task queue is a set of tasks.

总而言之,规范只要求每个事件循环至少有一个任务队列。他们不会详细说明 任务队列 ,而是使用 任务源 ,用户代理 (UA) 将 link到他们认为合适的任何任务队列,只要每个任务源中的任务按顺序执行。

在 HTML 规范中使用了许多 任务源 ,例如 here is a list of the generic ones, but every specs can define there own, just like the Message API,其中每个 MessagePort对象将有自己的任务源! (这意味着 UA 可以将这些消息 任务源 中的每一个映射到不同的 任务队列 )。

获取所有 任务来源的详尽列表 是不可能的,而且也不是很有用,因为它们实际上可能都以相同且仅 结尾任务队列.


我们需要查看的规范的另一个非常重要的部分是 event loop processing model

此算法定义了事件循环在每次迭代时应经历的所有步骤。
乍一看有点复杂,而且随着时间的推移它并没有简化,因为越来越多的上下文都遵循这个模型,需要处理非常不同的问题(看看你在 Worker 中的 OffscreenCanvas...).

但是,对于我们在这里感兴趣的内容,让我们假设它很简单并且我们处于 Window 上下文中。

The first step 是规范设计任务优先级的地方:

Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner [...]

就在这里,他们对 UA 说他们有权选择从哪个 任务队列 中选择下一个任务。这意味着,例如,如果他们有一个专用的 任务队列 用于 用户交互任务源 ,而另一个仅用于 networking task source,并且两者都包含等待中的任务,然后他们可以自由选择他们喜欢的第一个运行。

现在关于渲染,如果我们看看处理模型在执行此任务后如何进行,所有微任务也是如此进行了一系列测量,我们看到在 第 11 步 ,它们应该 update the rendering。这实际上是所有事件循环迭代的一部分(在 Window 上下文中),但该算法的第一步是定义这是否确实是一个渲染帧,这意味着大多数时候它会提前退出什么都不做。
但是,当它是渲染帧时,它必须执行所有渲染步骤,作为事件循环迭代的一部分,即可能在执行 正常 任务之后。
所以从规范的角度来看,我们不能在这里真正谈论优先级,它只是每个事件循环迭代的一部分,就像微任务检查点一样,它不会回到步骤 1 在他们可以选择要执行的任务的地方,他们被迫执行事件循环的那一部分。 尽管从技术上讲,在渲染步骤中,用户代理也负责确定它何时具有 "rendering opportunity",因此如果他们愿意,他们实际上可以设置该优先级。

所以对于我们网络作者来说,这意味着是的,我们可以在 setTimeout(fn, 0) 一个(或任何其他任务)之前有一个 requestAnimationFrame 回调触发,至少在确实调用此 requestAnimationFrame() 方法的一项任务本身在 渲染帧 的开头执行。

这实际上可能经常发生,这里有一个小片段应该可以在 Firefox 中非常可靠地发生,有时在 Chrome:

/*
  In latest FF and Chrome browsers, UI events like mousemove are being throttled by the UA.
  (as recommended by the specs)
  They make the limit rate match the one of the screen-refresh, like resize or scroll events.
  However, at least in Firefox they don't make it part of the 'update the rendering' algorithm,
  but execute it as a normal task.
  So a 'mousemove' event in this browser actually gives us accesss to the first step of a 'rendering frame'.
  
  This simple snippet tries to demonstrate that,
  if successful, "rAF" should always be logged first.
*/
onmousemove = (evt) => {
  console.clear();
  setTimeout( () => console.log( 'timeout' ), 0 );
  requestAnimationFrame( () => console.log( 'rAF' ) );
};
move your mouse over the frame


现在我们可以回答您的所有观点:

1) render callback is given the highest priority. Is it true?

正如我们刚刚演示的那样,并非如此,尽管渲染回调可能会在与调度它们的任务相同的事件循环迭代中执行,但我们不能在这里真正谈论优先级。

2) Does render queue exist as separate queue or is it alias for render callbacks?

规格,不定义任何特殊的任务队列,但也没有任务源用于渲染。您可能指的 update the rendering algorithm is made of a lot of different tasks to execute though, and in there lies the run the animation frame callbacks 命令。但是这些回调存储在一个Map中,而不是队列中。

3) Which callbacks are render? As I take in any repaint is render callback

都在 update the rendering 中,但您可能需要关注 步骤 5 之后的内容,因为之前只是一些检查。

4) Are there any other types of queues and if there are where can I read about them?

如前所述,规范未定义任务队列,并且任务源定义过于松散,无法提供完整列表。

但请记住,所有这些都是从规格的角度来看的。实施者可能会在很多方面偏离这个模型,并且模型本身足够松散以允许一大堆不同的设置。

举个例子,我想给你指出一个Firefox issue,它介绍了一个非常有趣的案例:
他们希望 setTimeout 回调的优先级低于其他任务,但仅在页面加载时 。为此,他们确实创建了一个新的任务队列,就是为了这个案例。
现在,在 Firefox 中,setTimeout 回调的优先级低于其他任务,但仅在页面加载时:

function test() {
  setTimeout( () => console.log('timeout'));
  onmessage = (evt) => console.log('message');
  postMessage('', '*');
}
console.log('at page load');
test();

setTimeout( () => {
  console.log('after page load');
  test();
}, 1000 );

/* results in Firefox:
at page load
message
timeout
after page load
timeout
message

Chrome will always print "message" first, but because they have a 2ms min timeout on setTimeout
*/

另一件需要注意的事情是可能即将到来的 Prioritized postTask API,我们已经可以在 Chrome 中使用它在 #enable-experimental-web-platform-features 标志下进行游戏,并且公开了一个 scheduler.postTask(fn, priority) 方法,它允许我们控制任务的优先级。