0


【异步系列三】10道 Promise 面试题彻底理解 Promise 异步执行顺序

前言

上一篇文章详细说明了关于 Promise 的概念以及执行顺序,Promise 作为 JavaScript 中的关键特性,无论是在日常工作中还是面试中,我们都必须掌握它。

前段时间在网上看到了一些关于 Promise 的面试题,质量很好,在这里整理到下面,并加上我一些自己的见解,欢迎斧正!

1. 同步代码块

console.log('1')const p =newPromise((resolve, reject)=>{
    console.log('2')})

console.log('3')

第一个面试题可谓是非常简单了,

  • 同步代码块是从上往下执行的
  • 当我们 new 一个 Promise 时,里面的起始函数会立即调用,而且里面的代码块是同步代码

所以这段代码依次输出结果为 1 2 3

2. 出现异步代码

console.log('start')

conse p =newPromise((resolve, reject)=>{
    console.log('1')resolve('2')})

p.then(res=>{
    console.log(res)})

console.log('end')

在这部分代码中,出现了一段异步代码:也就是 .then() 中的回调函数

要记住一个原则:同步先行,异步靠后,Javascript 引擎总是先执行同步代码,然后在执行异步代码。

此时,我们只需要区分同步代码和异步代码即可。

所以这段代码吗输出结果为: start 1 end 2

3. 有 resolve 的地方

console.log('strt')const p =newPromise((resolve, reject)=>{
    console.log(1)resolve(2)
    console.log(3)})

p.then(res=>{
    console.log(res)})

console.log('end')

这段代码与前面的代码基本一致,唯一区别是在 resolve() 之后有一个 console.log()

要记住:resolve 方法不会中断函数的执行,它背后的代码仍然会继续执行。

所以输出的结果是: start 1 3 end 2

4. 不被调用 resolve 的地方

console.log('start')const p =newPromise((resolve, reject)=>{
    console.log(1)})

p.then(res=>{
    console.log(2)})

console.log('end')

在这段代码中,resolve 方法从未被调用过,因此 p始终处于挂起状态 。 所以 p.then(…) 从未被执行过,故不会在控制台中打印。

所以输出的结果是: start 1 end

5. 带有函数的

console.log('start')constfn=()=>{returnnewPromise((resolve, reject)=>{
        console.log(1)resolve(2)})}

console.log(3)fn().then(res=>{
    console.log(res)})

console.log('end')

这段代码部分人开起来可能会有些迷惑,因为加了一层 fn

但我们要要记住,无论有多少层函数调用,我们的基本原则都是一样的:

  • 同步先行,异步靠后
  • 同步代码按照调用顺序执行

输出结果为 start 3 1 end 2

详细解析:

首先,第一步输出 start ,相信大家都没什么疑问。
fn 是一个函数,里面返回的是一个 new Promise 对象,new Promise 对象中的回调函数会立即执行,但由于此时 fn 函数还未调用,故先不执行。
接下来输出 3
下一行代码 fn() 函数调用,所以立即 打印 1
调用 resolve() 方法,这是一个异步代码,作为异步,需要等到同步代码执行完成后才会执行,故打印 end
同步代码全部执行完成后,执行异步代码, 打印 2
所以输出结果为 start 3 1 end 2

6. Fulfilling Promise

console.log('start')

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

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

console.log('end')

输出结果为: start end 1 2

详细解析:

首先,我们要明白 Promise.resolve(1) 这行代码
Promise.resolve方法返回一个promise的实例,状态为已完成
这行代码等价于:

new Promise((resolve) => { resolve(1) })

看到这里,是不是就明白了?
Promise.resolve(1) 是同步代码,但后面的 then() 是异步
所以输出结果为: start end 1 2

7. 宏任务 微任务

console.log('start')setTimeout(()=>{
    console.log('setTimeout')},0)

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

console.log('end')

详细解析:

这段代码相比较上一个来说,多了一个定时器,我们知道,定时器也是一个异步操作。那么这时候问题就来了,同步代码是按照调用顺序执行的,那么异步回调函数时按照什么顺序执行的呢?大家可以参考下我的上一篇博客:Promise原理解析及执行顺序。

在上面的代码中,setTimeout的定时器是0秒,Promise.resolve() 也会在执行后立即返回一个已完成的 Promise 对象。

两个异步任务都是立即完成的,那么谁的回调函数会先执行呢?

有人会说 setTimeout 在前面,所以先打印 setTimeout,然后打印 resolve,实际上,当我们在控制台运行下代码时会发现,实际情况并不是我们想的那样。

它们是有优先级的。

微任务 > 宏任务

  • 具有较高优先级的任务叫做微任务。包括:Promise、ObjectObserver、MutationObserver、Process.nextTick、async/await。
  • 优先级较低的任务称为宏任务。包括:setTimeout、setInterval、XHR

虽然 setTimeout 和 Promise.resolve() 是同时完成的,甚至 setTimeout 的代码写在前面,但是由于它的优先级较低,所以它的回调函数是在后面执行的。

所以输出结果是:start end resolve success

8. 微任务 + 宏任务 升级

const p =newPromise((resolve, reject)=>{
    console.log(1)setTimeout(()=>{
        console.log('timerStart')resolve('resolve')
        console.log('timerEnd')},0)
    console.log(2)})

p.then((res)=>{
    console.log(res)})

console.log('end')

详细解析:

首先执行同步代码: 1 2 end

然后找到 微任务 和 宏任务

从上一个代码解析中我们知道,定时器是宏任务,Promise 是微任务:

但这里有一个陷阱: 当前的promise 还处于 pending 状态,所以promise.then() 这部分代码(微任务)暂时不会被执行。

所以会先执行定时器中的回调函数,然后在执行微任务。

所以输出结果为: 1 2 end timerStart timerEnd resolve

9. 微任务 宏任务 交替执行排序

const timer1 =setTimeout(()=>{
    console.log(1)const promise1 = Promise.resolve().then(()=>{
        console.log('promise1')})},0)const timer2 =setTimeout(()=>{
    console.log('timer2')},0)

输出结果为: timer1 promise1 tiemr2

详细解析:

关于 宏任务 和 微任务,正确的理解是:

  1. 首先执行所有的微任务
  2. 执行宏任务
  3. 再次执行所有(新添加的)微任务
  4. 执行下一个宏任务

在上面代码中,没有和宏任务同步的微任务,所以开始执行 timer1 这个宏任务;

在 timer1 的宏任务中,新增了一个微任务,且该promise 对象的状态已经变为 fufilled(已成功),所以Promise.then() 的回调函数会在第二个宏任务 setTimeout 的回调函数之前执行。

所以执行结果为: timer1 promise1 tiemr2

10 最终升级版

console.log('start')const promise1 = Promise.resolve().then(()=>{
    console.log('promise1')const timer2 =setTimeout(()=>{
        console.log('timer2')},0)})const timer1 =setTimeout(()=>{
    console.log('timer1')const promise2 = Promise.resolve().then(()=>{
        console.log('promise2')})},0)

console.log('end')

如果能够正确写出答案且能说明原因的话,说明你对 Promise 的理解已经很强了,面试基本上没有问题。

详细解析:

按照以下思路进行思考:

  1. 同步代码
  2. 所有微任务
  3. 第一个宏任务
  4. 所有新添加的微任务
  5. 下一个宏任务

① 执行所有同步代码:

打印 start end

② 执行所有微任务

在这里插入图片描述

在这里打印 promise1

③ 执行第一个宏任务

在这里插入图片描述

在这里打印 timer1

注意:在这一步中,宏任务将一个新的为任务添加到任务队列中

④ 执行所有新添加的微任务

在这里插入图片描述
在这里打印 promise2

⑤ 执行下一个宏任务

在这里插入图片描述
所以,最后输出结果为: start end promise1 timer1 promise2 timer2

结论

在面试中,如果有类似的面试题,只需要记住一下三个核心要点,问题即可迎刃而解。

  1. JavaScript 引擎总是先执行同步代码,然后在执行异步代码(同步先行,异步靠后)
  2. 微任务的优先级高于宏任务
  3. 微任务可以在 Event Loop 中插队。

本文转载自: https://blog.csdn.net/qq_41131745/article/details/127004662
版权归原作者 小刘加油! 所有, 如有侵权,请联系我们删除。

“【异步系列三】10道 Promise 面试题彻底理解 Promise 异步执行顺序”的评论:

还没有评论