Promise和setTimeout执⾏顺序⾯试题
看到过下⾯这样⼀道题:
(function test() {
setTimeout(function() {console.log(4)}, 0);
new Promise(function executor(resolve) {
console.log(1);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(2);
}).then(function() {
console.log(5);
pending});
console.log(3);
})()
为什么输出结果是 1,2,3,5,4 ⽽⾮ 1,2,3,4,5 ?
⽐较难回答,但我们可以⾸先说⼀说可以从输出结果反推出的结论:
1. Promise.then 是异步执⾏的,⽽创建Promise实例( executor )是同步执⾏的。
2. setTimeout 的异步和 Promise.then 的异步看起来 “不太⼀样” ——⾄少是不在同⼀个队列中。
相关规范摘录
在解答问题前,我们必须先去了解相关的知识。(这部分相当枯燥,想看结论的同学可以跳到最后即可。)
Promise/A+ 规范
要想到原因,最⾃然的做法就是去看规范。我们⾸先去看看  。
摘录 promise.then 相关的部分如下:
promise.then(onFulfilled, onRejected)
2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [
3.1].
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement
ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver Tick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
规范要求, onFulfilled 必须在 执⾏上下⽂栈(execution context stack) 只包含 平台代码(platform code) 后才能执⾏。平台代码指引擎,环境,Promise实现代码。实践上来说,这个要求保证了 onFulfilled 的异步执⾏(以全新的栈),在 then 被调⽤的这个事件循环之后。
规范的实现可以通过 macro-task 机制,⽐如 setTimeout 和 setImmediate ,或者 micro-task 机制,⽐如 MutationObserver 或
者 Tick 。因为promise的实现被认为是平台代码,所以可以⾃⼰包涵⼀个 task-scheduling 队列或者 trampoline 。
通过对规范的翻译和解读,我们可以确定的是 promise.then 是异步的,但它的实现⼜是平台相关的。要继续解答我们的疑问,必须理解下⾯⼏个概念:
1. Event Loop,应该算是⼀个前置的概念,理解它才能理解浏览器的异步⼯作流程。
2. macro-task 机制和 micro-task 机制,这组概念很新,之前根本没听过,但却是解决问题的核⼼。
Event Loop 规范
规范⾥有 Event loops 这⼀章节(读起来⽐较晦涩,只关注相关部分即可)。
1. 每个浏览器环境,⾄多有⼀个event loop。
2. ⼀个event loop可以有1个或多个task queue。
3. ⼀个task queue是⼀列有序的task,⽤来做以下⼯作: Events task, Parsing task, Callbacks task, Using a
resource task, Reacting to DOM manipulation task等。
每个task都有⾃⼰相关的document,⽐如⼀个task在某个element的上下⽂中进⼊队列,那么它的document就是这个element的document。
每个task定义时都有⼀个task source,从同⼀个task source来的task必须放到同⼀个task queue,从不同源来的则被添加到不同队列。
每个(task source对应的)task queue都保证⾃⼰队列的先进先出的执⾏顺序,但event loop的每个turn,是由浏览器决定从哪个task source挑选task。这允许浏览器为不同的task source设置不同的优先级,⽐如为⽤户交互设置更⾼优先级来使⽤户感觉流畅。
Jobs and Job Queues 规范
本来应该接着上⾯Event Loop的话题继续深⼊,讲macro-task和micro-task,但先不急,我们跳到  规范,看看 Jobs and Job Queues 这⼀新增的概念,它有点类似于上⾯提到的 task queue 。
⼀个 Job Queue 是⼀个先进先出的队列。⼀个ECMAScript实现必须⾄少包含以下两个 Job Queue :
Name Purpose
ScriptJobs Jobs that validate and evaluate ECMAScript Script and Module source text. See clauses 10 and 15.
PromiseJobs Jobs that are responses to the settlement of a Promise (see 25.4).
单个 Job Queue 中的PendingJob总是按序(先进先出)执⾏,但多个 Job Queue 可能会交错执⾏。
跟随PromiseJobs到25.4章节,可以看到  :
这⾥我们看到, promise.then 的执⾏其实是向 PromiseJobs 添加Job。
event loop怎么处理tasks和microtasks?
好了,现在可以让我们真正来深⼊task(macro-task)和micro-task。
认真说,规范并没有包括macro-task 和 micro-task这部分概念的描述,但阅读⼀些⼤神的博⽂以及从规范相关概念推测,以下所提到的在我看来,是合理的解释。但是请看⽂章的同学辩证和批判地看。
⾸先, micro-task在ES2015规范中称为Job。 其次,macro-task代指task。
哇,所以我们可以结合前⾯的规范,来讲⼀讲Event Loop(事件循环)是怎么来处理task和microtask的了。
1. 每个线程有⾃⼰的事件循环,所以每个web worker有⾃⼰的,所以它才可以独⽴执⾏。然⽽,所有同属⼀个origin的windows共享⼀
个事件循环,所以它们可以同步交流。
2. 事件循环不间断在跑,执⾏任何进⼊队列的task。
3. ⼀个事件循环可以有多个task source,每个task source保证⾃⼰的任务列表的执⾏顺序,但由浏览器在(事件循环的)每轮中挑选
某个task source的task。
4. tasks are scheduled,所以浏览器可以从内部到JS/DOM,保证动作按序发⽣。在tasks之间,浏览器可能会render updates。从
⿏标点击到事件回调需要schedule task,解析html,setTimeout这些都需要。
5. microtasks are scheduled,经常是为需要直接在当前脚本执⾏完后⽴即发⽣的事,⽐如async某些动作但不必承担新开task的弊
端。microtask queue在回调之后执⾏,只要没有其它JS在执⾏中,并且在每个task的结尾。microtask中添加的microtask也被添加到microtask queue的末尾并处理。microtask包括 mutation observer callbacks 和 promise callbacks 。
结论
定位到开头的题⽬,流程如下:
1. 当前task运⾏,执⾏代码。⾸先 setTimeout 的callback被添加到tasks queue中;
2. 实例化promise,输出 1 ; promise resolved;输出 2 ;
3. promise.then 的callback被添加到microtasks queue中;
4. 输出 3 ;
5. 已到当前task的end,执⾏microtasks,输出 5 ;
6. 执⾏下⼀个task,输出 4 。
标签:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。