EventLoop 中微任务的执行顺序

前言

我相信即将参加各类面试的大家一定都对 EventLoop 有一定程度的了解,本文主要针对如何应试的面对各类 EventLoop 的执行过程面试题,其中很多内容是作者自己经过多次试验得出的,可能不完全权威科学,但能帮助一些需要快速应对面试的小伙伴们直观地解决这一问题。

1. EventLoop 执行顺序

在 js 中,任务分为宏任务(macrotask)和微任务(microtask)。

在常见的问题中,宏任务中包含同步代码 script 和异步中的 setTimeout setInterval setImmediate,微任务包含 Promise.then。

 2. 多个Promise.then的执行顺序

简单来说,Promise.then首先根据进入队列的顺序判断先后顺序,然后根据同一队列中执行快慢判断先后顺序。(还是通过举例说明更能明白这个规律。)

console.log('start');
let p = Promise.resolve('baz')
let p2 = p.then((res)=>{
    console.log('res');
    return new Promise(resolve =>resolve(res))
})
p2.then(a => console.log(a) )
const promise = new Promise((resolve) => {
    console.log('bar1');
    resolve('bac')
    console.log('bar2');
})

let promise2 = promise.then(res =>{
		console.log(res);
    return res
})
promise2.then(()=>console.log('aaa'))

看上去很复杂,但我们分析一下就清楚了:

1. 执行同步代码 console.log('start'),打印 start

2. Promise.resolve('baz') 是实例化过程,也是同步执行(此时 p 状态为 fulfilled)

3. 将 p.then 放入微任务队列

4. 将 p2.then 放入微任务队列

5. new Promise() 是实例化过程,同步执行,打印 bar1 -> 更改promise 状态为 fulfilled -> 打印 bar2

6. 将 promise.then 放入微任务队列

7.将 promise2.then 放入微任务队列

此时微任务队列中有四个任务,可以思考一下这四个任务的执行顺序:

任务p.thenp2.thenpromise.thenpromise2.then
打印结果resbazbacaaa

答案是:res -> bac -> aaa -> baz

这很明显不完全是按照放入顺序执行的,那我们还要关注什么地方呢?

Promise.then 中 Promise 的状态变更顺序。

Promise 的状态决定 Promise.then 的执行顺序,若状态为 pending,执行靠后;若状态为 fulfilled ,执行靠前。(两种相对而言)

那么我们就可以根据这点进行判断了:p 与 promise 的状态都为 fulfilled ,而 p2 与 promise2 的状态都为 pending ,因此先执行 p.then 和 promise.then 。那么这两者之间有先后顺序吗?实际上他们都是通过 resolve() 函数改变的状态,没有进一步的先后区别,因此根据放入队列的顺序执行。

现在队列中就剩下两个任务了,他们之间的顺序该如何判断呢?首先我们知道在 p.then 和 promise.then 执行之前 p2 与 promise2 的状态都为 pending 。但当这两个微任务执行后,p2 与 promise2 的状态就变为了 fulfilled,那么这两者之间有先后顺序吗?这里就和上面两者不同了,他们是有先后顺序。promise2 通过返回 非 Promise 对象 改变状态成 fulfilled,p2 通过返回 Promise 对象 改变状态成 fulfilled,前者的执行时间会快于后者。

若 .then 返回一个非 Promise 对象,其执行时间(变更状态的时间)会快于返回一个 Promise 对象。

 这样大家能明白为什么答案是:res -> bac -> aaa -> baz 了吗?

3. async/await 执行顺序

还是先总结一下:

1. await 只能写在 async 修饰的函数中;

2. 执行完 await 修饰的对象后,先执行 async 外的同步代码,然后再回到原处继续执行。

(还是通过举例说明)

async function test1(){
    console.log('start test1');
    console.log(await test2());
    console.log('end test1');
}
async function test2(){
    console.log('test2');
    return  'return test2 value'
}
test1();
console.log('start async');
setTimeout(()=>{
    console.log('setTimeout');
},0);
new Promise((resolve,reject)=>{
    console.log('promise1');
    resolve();
}).then(()=>{
    console.log('promise2');
})

分析如下:

1. 执行同步代码 test1(),进入到test1() 代码中,打印 start test1

2. 遇到 await 函数test2,先进入test2()代码中,打印 test2,返回值 'return test2 value'。此时 await 执行完毕,跳出这个 async 函数执行同步代码。

3.执行同步代码 console.log('start async'),打印 start async

4. 将 setTimeout 放入宏任务队列

5. new Promise() 是实例化过程,同步执行,打印 promise1,然后更改 Promise 状态为 fulfilled

6. 将 Promise.then 放入微任务队列。此时同步代码执行完毕,重新回到 await 执行完毕的地方。

7. 此时 console.log(await test2()) 中已变成了console.log('return test2 value'),打印 return test2 value

8. 执行同步代码 console.log('end test1'),打印 end test1

9. 此时所有同步代码执行完毕,查找微任务队列:执行 Promise.then,打印 promise2

10. 此时微任务队列为空,查找宏任务队列:执行 setTimeout,打印 setTimeout

遇到 await 就阻塞,执行完 async 外面的同步代码后,再回到内部。

好了,我们再来看看这道网易的笔试题:

(async ()=>{
    console.log(1);
    setTimeout(()=>{
        console.log(2);
    },0)
    await new Promise((resolve,reject)=>{
        console.log(3);
        resolve()
    }).then(()=>{
        console.log(4);
    })
    console.log(5);
})()

// 1 3 4 5 2

 分析如下:

1. 执行同步代码 console.log(1),打印 1

2. 将 setTimeout 放入宏任务队列

3. 遇到 await 修饰的 new Promise 函数,执行内部所有代码:执行同步代码打印 3 -> 更改 Promise 状态为 fulfilled -> 执行.then 打印 4

4. 执行完await 修饰的内部代码,跳出 async 执行同步代码(此处无)

5. 返回内部继续执行剩余同步代码,打印 5

6. 此时所有同步代码执行完毕,微任务队列为空,查找宏任务队列:执行 setTimeout,打印 2

可能大家疑惑的点在于为什么遇到 .then 这项微任务,也要一起执行呢?

await new Promise((resolve,reject)=>{
    console.log(3);
    resolve()
}).then(()=>{
    console.log(4);
})

其实,上述代码实际上可以拆分为下面这样:

let p = new Promise((resolve,reject)=>{
    console.log(3);
    resolve(4)
})
await p.then((res)=> {
    console.log(res)
})

有些读者看了可能会说,为什么不是 await 修饰 p 呢?也就是下面这样:

let p = await new Promise((resolve,reject)=>{
    console.log(3);
    resolve(4)
})
p.then((res)=> {
    console.log(res)
})

实际上这样写代码是一定报错的。因为 await 修饰的对象只会返回一个非 Promise 对象,而非 Promise 对象是不可能可以用 .then 的。

await 修饰的对象是 Promise 和 不是 Promise 的区别在于前者将 Promise 的结果值(即[[PromiseResult]]) 作为 await 结果,后者将 对象本身 作为 await 结果。

总而言之,await 结果只有可能是非 Promise 对象。因此我们也可以知道,await 若是修饰 Promise 对象,而该对象使用一个甚至多个 .then,实际上 await 修饰的是最后一个 .then 。 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值