日期:2023年2月7日

宏任务和微任务

现在有这样一段代码,你能说出它在控制台中的输出结果吗?

console.log(1)

Promise.resolve().then(() => {
    console.log(2)
})

console.log(3)

要真正的理解这一段代码,我们必须要先搞懂Promise中的实例方法then到底是在做什么?之前在学习Promise时,我们就已经说过了,then相当于为Promise设置了一个回调函数,当Promise中的数据处理完毕时,便会调用then所设置的回调函数来继续后续任务。

上例中,我们通过Promise.resolve()创建了一个理解完成的Promise,那么按道理讲then中的回调函数应该立刻执行啊?因为Promise已经完成了啊?所以打印的顺序不应该是“1 2 3”吗?如果能想到这些那么证明之前讲解的Promise你已经理解的差不多了,但是还不够准确!

then中的回调函数会在Promise完成后被调用,但是注意并不是立刻就调用,而是采用一种和定时器类似的处理方式,讲函数放入到一个任务队列中,而队列中的代码会在调用栈中的代码执行完毕后才会执行。也就是说then中的代码总是在当前调用栈中的代码执行完后才执行。所以上边代码的输出结果应该为:“1 3 2”

那么问题又来了,如果是这样的代码呢?

setTimeout(()=>{
    console.log(1)
})

Promise.resolve().then(() => {
    console.log(2)
})

错误的分析:setTimeout是定时器,它会在一段时间后将函数放入到任务队列中,而我们没有指定时间,也就意味着函数会立刻放入到任务队列中。then同样也是将函数放入到任务队列中,并且这个Promise是一个立即完成的Promise所以函数也是立刻进入任务队列。那么按照执行顺序来讲,定时器在前,then在后,所以定时器中的函数应该先进入队列,队列又是先进先出的,所以应该先1后2。

上边的分析看似合理,实际上是不对的。因为setTimeout和then虽然都将函数放入到队列中,但是却不是同一个队列。为了更合理的处理异步任务,ES标准规定了一个内部的队列“PromiseJobs”,这个队列是专门用来放置由Promise产生的回调函数的(then、catch、finally),这个队列我们通常被称为“微任务队列(microtask queue)”。相对的,setTimeout这些方法是将函数放入到了“宏任务队列(macrotask queue)”。

简单来说,任务队列有两个,宏任务队列和微任务队列。代码执行时,宏任务进入到宏任务队列,微任务进入到微任务队列。那么哪些任务时微任务,哪些任务是宏任务呢?其实大部分的任务都属于宏任务。而微任务通常在代码运行时产生,通常是由Promise所创建的,Promise的then、catch、finally中的回调函数会作为微任务进入到微任务队列中。

JS代码执行时,每一个宏任务执行完毕后,JS引擎会立即执行微任务队列中的所有任务,然后才是执行宏任务队列中的任务。换句话中then中的回调函数(微任务)会先于定时器中的回调函数(宏任务)执行。所以上例中代码的执行结果应该为:“2 1”。

如果上边的内容你理解了,可以尝试分析一下这段代码:

console.log(1);

setTimeout(() => console.log(2));

Promise.resolve().then(() => console.log(3));

Promise.resolve().then(() => setTimeout(() => console.log(4)));

Promise.resolve().then(() => console.log(5));

setTimeout(() => console.log(6));

console.log(7);
5 5 投票数
文章评分
订阅评论
提醒
guest

4 评论
最旧
最新 最多投票
内联反馈
查看所有评论
Nina3375
Nina3375
3 月 前

解释的清清楚楚,明明白白!点赞!👍👍

牛有才
牛有才
3 月 前

好文

超哥迷弟
超哥迷弟
2 月 前

李老师,settimeout是异步宏任务,promise.then(),就算settimeout写在前面,也是先执行promise.then

RanGuMo
1 月 前

1->7->3->5->2->6->4

4
0
希望看到您的想法,请您发表评论x