Open kangkai124 opened 5 years ago
首先需要明白,JavaScript 是一门单线程语言,分同步任务和异步任务。
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。
根据规范,事件循环是通过任务队列的机制来进行协调的。
一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。
setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。
(macro)task(又称之为宏任务),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
浏览器为了能够使得 JS 内部 (macro)task 与 DOM 任务能够有序的执行,会在一个 (macro)task 执行结束后,在下一个 (macro)task 执行开始前,对页面进行重新渲染,流程如下:
(macro)task->渲染->(macro)task->...
(macro)task 主要包含:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
microtask(又称为微任务),可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前 task 任务后,下一个 task 之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有 microtask 都执行完毕(在渲染前)。
microtask主要包含:Promise.then、MutaionObserver、process.nextTick(Node.js 环境)
事件循环,宏任务,微任务的关系如图所示:
Promise 中的异步体现在then
和catch
中,所以写在Promise中的代码是被当做同步任务立即执行的。
await 是一个让出线程的标志。await 后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。
由于因为async await 本身就是 promise+generator 的语法糖。所以 await 后面的代码是 microtask。所以
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
等价于
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(() => {
console.log('async1 end');
})
}
举个栗子:
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
首先,事件循环从宏任务(macrotask)队列开始,这个时候,宏任务队列中,只有一个script(整体代码)任务;当遇到任务源(task source)时,则会先分发任务到对应的任务队列中去。所以,上面例子的第一步执行如下图所示:
然后进入第一轮时间循环:
console.log(1)
,输出 1 setTimeout
,将其任务放在 macrotask 队列中,记为 setTimeout1
process.nextTick()
,放在 microtask 中,记为process1
Promise
,Promise 中的函数是立即执行的,所以输出 7,then
放在 microtask 中,记为 then1
setTimeout
,放在 macrotask 中,记为 setTimeout2
上图是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。我们发现了process1
和then1
两个微任务。
process1
,输出6then1
,输出8第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。
那么第二轮时间循环从setTimeout1
宏任务开始:
process.nextTick()
,将其放在 microtask 中,记为process2
new Promise
立即执行输出 4,then
也放在 microtask 中,记为then2
第二轮事件循环宏任务结束,我们发现有process2
和then2
两个微任务可以执行。
第二轮事件循环结束,第二轮输出2,4,3,5。
第三轮事件循环开始,此时只剩 setTimeout2
了,执行。
process.nextTick()
放在 microtask 中,记为process3
new Promise
,输出 11,将then
放在microtask 中,记为then3
第三轮事件循环宏任务执行结束,执行两个微任务process3
和then3
。
第三轮事件循环结束,第三轮输出9,11,10,12。
整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。
再举几个栗子吧
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('setTimeout'); }, 0)
async1();
new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');
2.
```js
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
//async2做出如下更改:
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise3');
resolve();
}).then(function() {
console.log('promise4');
});
console.log('script end');
async function async1() {
console.log('async1 start');
await async2();
//更改如下:
setTimeout(function() {
console.log('setTimeout1')
},0)
}
async function async2() {
//更改如下:
setTimeout(function() {
console.log('setTimeout2')
},0)
}
console.log('script start');
setTimeout(function() { console.log('setTimeout3'); }, 0) async1();
new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }); console.log('script end');
4.
```js
async function a1 () {
console.log('a1 start')
await a2()
console.log('a1 end')
}
async function a2 () {
console.log('a2')
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('promise1')
})
a1()
let promise2 = new Promise((resolve) => {
resolve('promise2.then')
console.log('promise2')
})
promise2.then((res) => {
console.log(res)
Promise.resolve().then(() => {
console.log('promise3')
})
})
console.log('script end')
注意 promise2
在 async1 end
之前
参考: