Tasks, microtasks, queues and schedules
Posted Rank-Bill
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tasks, microtasks, queues and schedules相关的知识,希望对你有一定的参考价值。
有些东西不时常提起很可能会遗忘掉,建议自己经常记录一些!非原文 表示 个人见解,文章翻译部分章节。 本文翻译自 https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ 感谢jake提供的分享,版权归原作者所有,仅供学习参考使用!
Try it
首先来看一段javascript的程序,及其输出的顺序
console.log('script start');
setTimeout(function()
console.log('setTimeout');
, 0);
Promise.resolve().then(function()
console.log('promise1');
).then(function()
console.log('promise2');
);
console.log('script end');
script start
script end
promise1
promise2
setTimeout
正确答案是 script start
, script end
, promise1
, promise2
, setTimeout
原文中提到可能浏览器的差异 可能setTimeout 会在 promise1 与 promise2之前打印,在firefox与Safari 8.0.7中总是正确的。(非原文:浏览器的差异本文中不考虑,具体查看原文)。
Why this happens
要理解这一点,您需要知道事件循环(event loop)如何处理任务和微任务。当你第一次遇到它的时候,你可能会有很多的想法。
请保持深呼吸……
每个“线程”都有自己的事件循环(event loop),因此每个web worker都有自己的事件循环(event loop),因此它可以独立执行,然而,同源上的所有windows窗口都共享一个事件循环(event loop),保证它们可以同步通信。事件循环(event loop)持续 执行队列中的任何任务。事件循环(event loop)可以有多个任务源,也需确保各个任务源的执行顺序(IndexedDB等规范定义了它们自己的执行顺序),但是浏览器可以决定在循环的每个回合中选择哪个源来执行任务(非本文:回答为什么会有不同的执行顺序)。这允许浏览器优先处理性能敏感的任务,比如用户输入等。
Task 任务被调度,这样浏览器就可以从内部获取JavaScript/DOM,并确保这些操作按顺序发生。在任务之间,浏览器可能呈现更新。从鼠标单击到事件回调需要调度任务,解析html以及上述setTimeout 一样。setTimeout等待一个给定的延迟,然后为它的回调安排一个新的任务。这就是为什么在script end后打印 setTimeout,因为打印script end 是第一个任务的一部分,并且setTimeout记录在一个单独的任务中。
MicroTask 微任务通常被安排在当前执行脚本之后应该立即发生的事情上,例如对一批操作做出反应,或者使某些东西异步执行,而不需要承担整个新任务的代价。只要没有其他JavaScript在执行过程中,并且在每个任务的末尾,微任务队列在回调之后被处理。在微任务期间排队等待的任何其他微任务都被添加到队列的末尾并被处理。微任务包括 mutation observer 回调, promise 回调等。 一旦一个promise设定了,或者它已经resolve,就把它加入microTask队列中。这确保了promise 回调是异步的,即使promise 已经resolve。调用 .then(yey, nay) 让promise任务立即步入 microtack的任务队列中。这就是为什么promise1和promise2会在 script end 之后被打印,因为当前运行的脚本必须在微任务处理之前完成。promise1和promise2在setTimeout之前被打印,因为微任务总是在下一个任务之前发生。
STEPS
一步一步的图示
console.log('script start')
Tasks | Run script | 执行JS 代码,打印输出script start |
MicroTasks | ||
JS stack | script | |
Log | script start |
setTimeout(function()
console.log('setTimeout')
,0)
Promise.resolve().then(function()
console.log('promise1');
).then(function()
console.log('promise2');
);
Tasks | Run script setTimeout-callback | setTimeout的回调会被加入到Task中, promise 回调加入到Microtasks中 |
MicroTasks | promise then | |
JS stack | script | |
Log | script start |
console.log('script end')
Tasks | Run script setTimeout-callback | 打印 script end, 已经到最后一行代码了 JS stack 中执行完成,开始执行micro task Run script一致存在一直在当前任务执行完成后 |
MicroTasks | promise then | |
JS stack | ||
Log | script start script end |
执行microTask任务
Tasks | Run script setTimeout callback | 执行完成第一个promise 后return undefined, 继续 将下一个promise callback 加入micro task |
MicroTasks | promise then promise then | |
JS stack | promise callback | |
Log | script start script end promise1 |
Tasks | Run script setTimeout callback | 执行完之后清空 |
MicroTasks | promise then | |
JS stack | ||
log | script start script end promise1 |
重复以上步骤,执行完promise then 打印 promise2 后
Tasks | Run script setTimeout callback |
MicroTasks | |
JS stack | |
Log | script start script end promise1 promise2 |
上面提到过:微任务总是在下一个任务之前发生 。所有的微任务执行完成后,在执行下一个任务之前(例如,浏览器预备重新渲染之前)此时第一个RunScript的任务也即是 执行完成了。
Tasks | setTimeout callback |
MicroTasks | |
JS stack | |
log | script start script end promise1 promise2 |
Tasks | setTimeout callback | setTimeout callback 任务进入JS stack |
MicroTasks | ||
JS stack | setTimeout callback | |
Log | script start script end promise1 promise2 setTimeout |
Tasks | 结束 | |
MicroTasks | ||
JS stack | ||
Log | script start script end promise1 promise2 setTimeout |
----------------------------------------------------------------------------------------------------------------------------------------
接下来继续分析另外一段代码
<div class="outer">
<div class="inner"></div>
</div>
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
// Let's listen for attribute changes on the
// outer element
new MutationObserver(function()
console.log('mutate');
).observe(outer,
attributes: true
);
// Here's a click listener…
function onClick()
console.log('click');
setTimeout(function()
console.log('timeout');
, 0);
Promise.resolve().then(function()
console.log('promise');
);
outer.setAttribute('data-random', Math.random());
// …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
mutationObser主要用来监听DOM的变动,callback会在每次DOM变动后调用,observe中第一个参数表示监听的DOM元元的article 第二个表示变动的类型 attribute:true表示监听属性的变动;前文提到过mutationObserve也是microTask。
继续分析下:触发click事件,setTimeout 是tasks, mutation observe和 promise是microtasks
Tasks | Dispatch click setTimeout callback
| 触发事件后,接收JS stack当前为onClick ,分析onClick函数中的代码执行将任务分配如下,console.log直接执行,输出click pormise 加入microtasks 修改DOM后mutationObserver的任务也会加入到microtask中 |
MicroTasks | promise then Mutationobservers | |
JS stack | onClick | |
Log | click |
onClick已经执行完,微任务总是在下一个任务之前发生
Tasks | Dispatch click setTimeout callback
| 在JS stack为空的时候执行下一个任务前执行 micro task,具体分析如上 |
MicroTasks | ||
JS stack | ||
Log | click promise mutate |
但是由于冒泡的原因 会再次的onCick回调
Tasks | Dispatch click setTimeout callback setTimeout callback | 注意新加的setTimeout callback |
MicroTasks | promise then mutation observers | |
JS stack | onClick | |
Log | click promise mutate click |
Tasks | Dispatch click setTimeout callback setTimeout callback | 执行逻辑同上 |
MicroTasks | ||
JS stack | ||
Log | click promise mutate click promise mutate |
Tasks |
| 没有微任务后会继续执行下一个任务 |
MicroTasks | ||
JS stack | ||
Log | click promise mutate click promise mutate timeout timeout |
---------------------------------------------------------------------------------------------------------
加深了印象之后进一步的深入
inner.click
注直接click的时候与Run Script是同步的,之前的是runScript 之后 dispatch click;在JS stack为空的时候执行下一个任务前执行 micro task
Tasks | Run script setTimeout callback | 在执行完innerClick后, 此时stack并没有空,冒泡 会触发另一个 onClick,此时仍然实在Run Script阶段 mutation observer已经pending无法加入再次加入microtask |
MicroTasks | promise then Mutation observe promise then | |
JS stack | script onClick | |
Log | click |
一切只有当同步的执行完之后,Run script结束 JS stack为空去执行下一个任务时会清空microtask 中的任务,
You made it!
总结:
1.Tasks 按顺序执行,浏览器可以在两个任务之间进行渲染
2.microTasks 按下规则执行:
在所有回调执行完成(如:冒泡时会触发两次回调) ,并且在没有其他的JavaScript在运行中的时候
在每一个任务的最后
以上是关于Tasks, microtasks, queues and schedules的主要内容,如果未能解决你的问题,请参考以下文章
Tasks, microtasks, queues and schedules
JavaScript tasks, microtasks, queues and schedules