js事件循环机制(浏览器端Event Loop) 以及async/await的理解
转载:https://segmentfault.com/a/1190000017554062
事件循环机制
理解js
的事件循环机制,能够很大程度的帮我们更深层次的理解平时遇到的一些很疑惑的问题
简单版本
下面来看一段代码,想想它的结果和你的结果是否一样
setTimeout(function() {
console.log(1)
}, 0)
console.log(2)
// 执行结果是 2 1
我们可以将js
的任务分为同步任务和异步任务, 按照这种分类js
的执行机制如下
- 任务执行队列分为同步任务队列和异步任务队列
- 代码执行时,遇到同步代码,会被直接推入同步任务队列并依次执行
- 遇到异步代码(如
setTimeout、setInterval
), 会被直接推入异步任务队列 - 当同步任务队列执行完毕,这个时候异步任务队列的任务会被依次推入同步任务队列并依次执行
所以上面的代码执行的时候, setTimeout()
不会被立即执行,会被推到异步任务队列里面, 之后再执行console.log(2)
, 同步任务队列任务执行完毕之后,会去异步任务队列的任务会被依次推到 同步任务队列并执行
终极版本
下面来看一段代码,想想它的结果和你的结果是否一样
setTimeout(function() {
console.log(1)
}, 0)
new Promise(function(resolve, reject) {
console.log(2)
resolve()
}).then((res) => {
console.log(3)
})
console.log(4)
// 执行结果是 2 4 3 1
js
异步任务按照准确的划分,应该将任务分为
- 宏任务:
setTimeout
、setInterval
- 微任务: 例如
Promise.then
方法。注意new Promsie()
的时候是同步,立即执行。
注意: 现在有三个队列: 同步队列(也称执行栈)、宏任务队列、微任务队列
所以针对这种机制,js
的事件循环机制应该是这样的
- 遇到同步代码,依次推入同步队列并执行
- 当遇到
setTimeout、setInterval
,会被推到宏任务队列 - 如果遇到
.then
,会被当作微任务,被推入微任务队列 - 同步队列执行完毕,然后会去微队列取任务,直到微队列清空。然后检查宏队列,去宏队列取任务,并且每一个宏任务执行完毕都会去微队列跑一遍,看看有没有新的微任务,有的话再把微任务清空。这样依次循环
console.log(1);
setTimeout(() => {
console.log('setTimeout');
}, 0);
let promise = new Promise(resolve => {
console.log(3);
resolve();
}).then(data => {
console.log(100);
}).then(data => {
console.log(200);
});
console.log(2);
所以对于以上的代码执行流程如下:
- 遇到同步任务先输出1。
setTimeout
是宏任务,会先放到宏任务队列中。new Promise
是立即执行的,所以会先输出3。- 而
Promise.then
是微任务,会依次排列到微任务队列中,继续向下执行输出2。 - 现在执行栈中的任务已经清空,再将微任务队列清空,依次输出100和200。
- 然后每次取出一个宏任务,因为现在只有一个宏任务,所以最后输出
setTimeout
。
async/await
async
当我们在函数前使用async
的时候,使得该函数返回的是一个Promise
对象
async function test() {
return 1 // async的函数会在这里帮我们隐士使用Promise.resolve(1)
}
// 等价于下面的代码
function test() {
return new Promise(function(resolve, reject) {
resolve(1)
})
}
可见async
只是一个语法糖,只是帮助我们返回一个Promise
而已
await
await
表示等待,是右侧「表达式」的结果,这个表达式的计算结果可以是 Promise 对象的值或者一个函数的值(换句话说,就是没有特殊限定)。并且只能在带有async
的内部使用
使用await
时,会从右往左执行,当遇到await
时,会阻塞函数内部处于它后面的代码,去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余的代码, 并且当await
执行完毕之后,会先处理微任务队列的代码
下面来看一个栗子:
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' )
下面是在chrome
浏览器上输出的结果
使用事件循环机制分析:
- 首先执行同步代码,
console.log( 'script start' )
- 遇到
setTimeout
,会被推入宏任务队列 - 执行
async1()
, 它也是同步的,只是返回值是Promise
,在内部首先执行console.log( 'async1 start' )
- 然后执行
async2()
, 然后会打印console.log( 'async2' )
- 从右到左会执行, 当遇到
await
的时候,阻塞后面的代码,去外部执行同步代码 - 进入
new Promise
,打印console.log( 'promise1' )
- 将
.then
放入事件循环的微任务队列 - 继续执行,打印
console.log( 'script end' )
- 外部同步代码执行完毕,接着回到
async1()
内部, 由于async2()
其实是返回一个Promise
,await async2()
相当于获取它的值,其实就相当于这段代码Promise.resolve(undefined).then((undefined) => {})
,所以.then
会被推入微任务队列, 所以现在微任务队列会有两个任务。接下来处理微任务队列,打印console.log( 'promise2' )
,后面一个.then
不会有任何打印,但是会执行 - 执行后面的代码, 打印
console.log( 'async1 end' )
- 进入第二次事件循环,执行宏任务队列, 打印
console.log( 'setTimeout' )