本次分享一下从HTML5与PromiseA+规范来迅速理解一波事件循环中的microtask 与macrotask。javascript
欢迎关注个人博客,不按期更新中——html
——什么时候完结不肯定,写多少看我会多少!这是已经更新的地址:java
这个系列旨在对一些人们不经常使用遇到的知识点,以及可能经常使用到但未曾深刻了解的部分作一个从新梳理,虽然可能有些部分看起来没有什么用,由于平时开发真的用不到!但我的认为糟粕也好精华也罢里面所有蕴藏着JS一些偏本质的东西或者说底层规范,若是能适当避开温馨区来看这些小细节,也许对本身也会有些帮助~文章更新在个人博客,欢迎不按期关注。node
setTimeout(function() {
console.log('setTimeout1');
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
})
}, 0);
setTimeout(function() {
console.log('setTimeout2');
Promise.resolve().then(function() {
console.log('promise3');
}).then(function() {
console.log('promise4');
})
}, 0);复制代码
从这段代码中咱们发现里面有两个定时器setTimeout
,每一个定时器中还嵌套了Promise。我相信熟悉microtask 与macrotask任务队列的童鞋能很快的知晓答案,这个东西给个人感受就是清者自清。git
/* 请在新版chrome中打印结果
setTimeout1
promise1
promise2
setTimeout2
promise3
promise4
*/复制代码
不作解释直接看下规范中怎么说的:github
There must be at least one browsing context event loop per user agent, and at most one per unit of related similar-origin browsing contexts. An event loop has one or more task queues.web
一个浏览器环境下只能有一个事件循环,同时循环中是能够存在多个任务队列的。
同时咱们接着看规范中对event-loop执行过程是如何规定的:chrome
1.Let oldestTask be the oldest task on one of the event loop's task queues.api
2.Set the event loop's currently running task to oldestTask.promise
3.Run oldestTask.
4.Set the event loop's currently running task back to null.
5.Remove oldestTask from its task queue.
6.Microtasks: Perform a microtask checkpoint.
7.Update the rendering
其中的task queues,就是以前提到的macrotask,中文能够翻译为宏任务。顾名思义也就是正常的一些回调执行,好比IO,setTimeout等。简单来讲当事件循环开始后,会将task queues最早进栈的任务执行,以后移出,进行到第六步,作microtask的检测。发现有microtask的任务那么会依照以下方式执行:
While the event loop's microtask queue is not empty:
//当microtask队列中还有任务时,按照下面执行
1.Let oldestMicrotask be the oldest microtask on the event loop's microtask queue.
2.Set the event loop's currently running task to oldestMicrotask.
3.Run oldestMicrotask.
4.Set the event loop's currently running task back to null.
5.Remove oldestMicrotask from the microtask queue.
从这段规范能够看出,当执行了一个macrotask后会有一个循环来检查microtask队列中是否还存在任务,若是有就执行。这说明执行了一个macrotask(宏任务)以后,会执行全部注册了的microtask(微任务)。
一块儿看起来很正常对吧?
那么若是微任务“嵌套”了呢?就像一开始做者给出的那段代码同样,promise调用了不少次.then方法。这种状况文档中有作出规定么?有的。
If, while a compound microtask is running, the user agent is required to execute a compound microtask subtask to run a series of steps, the user agent must run the following steps:
1.Let parent be the event loop's currently running task (the currently running compound microtask).
2.Let subtask be a new task that consists of running the given series of steps. The task source of such a microtask is the microtask task source. This is a compound microtask subtask.
3.Set the event loop's currently running task to subtask.
4.Run subtask.
5.Set the event loop's currently running task back to parent.
简单来讲若是有“嵌套”的状况,注册的任务都是microtask,那么就会一股脑得所有执行。
经过上面对文档的解读咱们能够知道如下几件事:
那么还剩一件事情就是什么任务是macrotask,什么是microtask?
可是!我对于setImmediate与process.nextTick的行为持怀疑态度。理由最后说!不过在浏览器运行环境中咱们不须要关系上面那两种事件。
在本文一开始就提出,这段代码要在新版chrome中运行才会获得正确结果。那么不在chrome中呢?
举个例子,别的做者不一一测试了,这是safari中的结果。咱们能够看到顺序被打乱了。so为何我执行了同样的代码结果却不一样?
我的认为若出现结果不一样的状况是因为不一样执行环境(chrome, safari, node .etc)将回调须要执行的任务所划分到的任务队列与PromiseA+规范中所提到的任务队列中的任务划分准则执行不一致致使的。也就是Promise可能被划分到了macrotask中。有兴趣深刻了解的童鞋能够看下这篇tasks-microtasks-queues-and-schedules.
细心的童鞋可能发现我一直强调的js运行环境是浏览器下的事件循环状况。那么node中呢?
setTimeout(function() {
console.log('setTimeout1');
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
})
}, 0);
setTimeout(function() {
console.log('setTimeout2');
Promise.resolve().then(function() {
console.log('promise3');
}).then(function() {
console.log('promise4');
})
}, 0);复制代码
仍是这段代码,打印出来会不会有区别?多打印几回结果同样么?为何会这样?
我只能理解到node经过libuv实现事件循环的方式与规范没有关系,但具体为何会打印出不一样的效果。。求大神@我
惯例po做者的博客,不定时更新中——有问题欢迎在issues下交流,捂脸求star=。=