在写代码的时候常常思考一个问题,究竟是那个函数先执行,自己JavaScript是一门单线程的语言,意思就是按照顺序执行。可是加入一些setTimeout和promise的函数来又实现了异步操做,经常我会写一个setTimeout(fn,0),他会当即执行吗?promise
首先咱们先来看一段代码:异步
<script>
console.log("Start");
setTimeout(function(){
console.log("SetTimeout");
},0);
new Promise(function(resolve,reject){
console.log("Promise");
resolve();
}).then(function(){
console.log("Then");
});
console.log("End");
<script> 复制代码
这些日志的打印顺序是:async
Start
Promise
End
Then
SetTimeout
复制代码
这是为何函数
首先,咱们知道JavaScript的一大特色就是单线程,而这个线程中拥有惟一的一个事件循环。oop
一个线程中,事件循环是惟一的,可是任务队列能够拥有多个。ui
任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。spa
宏任务线程
微任务日志
事件循环的顺序,决定js代码的执行顺序。一段代码块就是一个宏任务。进入总体代码(宏任务)后,开始第一次循环。接着执行全部的微任务。而后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行全部的微任务。code
主线程(宏任务) => 微任务 => 宏任务 => 主线程
下图是简易版的事件循环:
因此在上面的代码中宏任务有script代码块,setTimeout,微任务有Promise
事件循环流程分析以下:
script
做为第一个宏任务进入主线程,遇到console.log,输出Start
。setTimeout
,其回调函数被分发到宏任务Event Queue中。Promise
,new Promise直接执行,输出Promise。then被分发到微任务Event Queue中。End
。script
做为第一个宏任务执行结束,看看有哪些微任务?咱们发现了then
在微任务Event Queue里面,执行setTimeout
对应的回调函数,当即执行。提升下难度在来一段较为复杂的代码来检验是否已经基本了解了事件循环的机制
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout1');
}, 200);
setTimeout(function() {
console.log('setTimeout2');
new Promise(function(resolve) {
resolve();
}).then(function() {
console.log('then1')
})
new Promise(function(resolve) {
console.log('Promise1');
resolve();
}).then(function() {
console.log('then2')
})
},0)
async1();
new Promise(function(resolve) {
console.log('promise2');
resolve();
}).then(function() {
console.log('then3');
});
console.log('script end');
复制代码
第一轮事件循环流程分析以下:
总体script做为第一个宏任务进入主线程,async1(),和async12()函数申明,但并无执行,遇到console.log
输出script start
。
继续向下执行,遇到setTimeout
,把它的回调函数放入宏任务Event Queue。(ps:暂且叫他setTimeout1)
宏任务 | 微任务 |
---|---|
setTimeout1 | 1 |
继续向下执行,又遇到一个setTimeout
,继续将他放入宏任务Event Queue。(ps:暂且叫他setTimeout2)
宏任务 | 微任务 |
---|---|
setTimeout1 | |
setTimeout2 |
遇到执行async1(), 进入async
的执行上下文以后,遇到console.log
输出 async1 start
而后遇到await async2(),因为()
的优先级高,全部当即执行async2()
,进入async2()
的执行上下文。
看到console.log
输出async2
,以后没有返回值,结束函数,返回undefined,返回async1
的执行上下文的await undefined,因为async
函数使用await
后得语句会被放入一个回调函数中,因此把下面的放入微任务Event Queue中。
宏任务 | 微任务 |
---|---|
setTimeout1 | async1 => awati 后面的语句 |
setTimeout2 |
结束async1()
遇到Promise
,new Promise直接执行,输出Promise2
。then
后面的函数被分发到微任务Event Queue中
宏任务 | 微任务 |
---|---|
setTimeout1 | async1 => awati 后面的语句 |
setTimeout2 | new Promise() => 后的then |
执行完Promise()
,遇到console.log
,输出script end
,这里一个宏任务代码块执行完毕。
在主线程执行的过程当中,事件触发线程一直在监听着异步事件, 当主线程空闲下来后,若微任务队列中有任务未执行,执行的事件队列(Event Queue)中有微任务,遇到new Promise()
后面的回调函数,执行代码,输出then3
。
看到 async1
中await
后面的回调函数,执行代码,输出async1 end
(注意:若是俩个微任务的优先级相同那么任务队列自上而下执行,可是promise的优先级高于async,因此先执行promise后面的回调函数)
自此,第一轮事件循环正式结束,这一轮的结果是输出:script start => async1 start => async2 => promise2 => script end => then3 => async1 end
宏任务 | 微任务 |
---|---|
setTimeout1 | |
setTimeout2 |
那么第二轮时间循环从setTimeout宏任务开始:
setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。若是到了,就执行对应的代码;若是不到,就等到再下一轮Event Loop时从新判断。由于setTimeout1有200ms的延时,并没到达指定时间,因此先执行setTimeout2这个宏任务
进入到setTimeout2,遇到console.log
首先输出setTimeout2
;
遇到Promise
,new Promise直接执行。then
后面的函数被分发到微任务Event Queue中
宏任务 | 微任务 |
---|---|
setTimeout1 | new Promise() => 后的then1 |
再次遇到Promise
,new Promise直接执行输出promise1
。then
后面的函数被分发到微任务Event Queue中
宏任务 | 微任务 |
---|---|
setTimeout1 | new Promise() => 后的then1 |
空 | new Promise() => 后的then2 |
主线程执行执行空闲,开始执行微任务队列中依次输出then1
和then2
。
第二轮事件循环正式结束。第二轮依次输出promise1 => then1 => then2
如今任务队列中只有个延时200ms的setTimeout1,在到达200ms后执行setTimeout的回调函数输出setTimeout1
时间循环结束
整段代码,完整的输出为script start => async1 start => async2 => promise2 => script end => then3 => async1 end => promise1 => then1 => then2 => setTimeout1
因此在咱们写下setTimeout(fn,0)
的时候他并非在当时当即执行,是从下一个Event loop
开始执行,便是等当前全部脚本执行完再运行,就是"尽量早"。