这道易错js执行顺序的题,隐藏的点你get到了吗?

1,378 阅读4分钟

前言

  • 面试的时候经常碰到一些关于js执行顺序的题,涉及到Event-loop,微任务、宏任务、任务队列之类的,有的题比较简单,前段时间在交流群里,群友贴出来一题,我觉得蛮有意思,当时做了一下,给出了简单的解析 ,事后回想,解析的比较笼统,今天我们来扒衣扒皮,来我们直接上题,

上代码

  console.log('script start')
  async function async1() {
    await async2()
    console.log('async1 end')
  }
  async function async2() {
    console.log('async2 end')
  }
  async1()

  setTimeout(function() {
    console.log('setTimeout')
  },0)

  new Promise(resolve => {
    console.log('promise')
    resolve()
  }).then(function() {
    console.log('promise1')
  }).then(function() {
    console.log('promise2')
  })

  console.log('script end')
  • 这题乍一看很长啊,让我们来逐行解析一下(执行环境google浏览器)

  • 当时在群里解析的答案: 首先立即执行的,script start 然后调用async1 输出 async2 end ,async1里面的console在await之后,微任务,挂起, 然后碰到setti宏任务,挂起,然后执行new promise的立即执行 console输出promise,。then为微任务,挂起,然后执行script end, 前面挂起了三个微任务,执行顺序为先进先出,saync1 end promise1 promise2 , 最后执行当前的宏任务 settimeout

  • 你觉得有哪里解释的不够清晰,涉及到的知识点,微任务,宏任务的执行顺序 我们来拆解一下这题

  console.log('script start')
  setTimeout(function() {
    console.log('setTimeout')
  },0)

  new Promise(resolve => {
    console.log('promise)
    resolve()
  }).then(function() {
    console.log('promise1')
  }).then(function() {
    console.log('promise2')
  })
  console.log('script end')

这样写,我觉得大多数了解js运行机制的掘金炮友都知道答案,也基本上都能解释出来(这里不着重讲微任务宏任务,详情看掘金其他炮友的文章,都写的很好)。

// 控制台输出
script start
promise
script end 
promise1
promise2
setTimeout

有意思async、await

  • 我觉得这里有意思的点是async 和await 的执行顺序,以及执行的里面的一些规则,所以我把他单独拿出来解析,
async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

控制台输出:
async2 end
async1 end
  • 如果把这个加到上面的拆解片段中,我想大部分人基本上都能猜出答案,但是别人要问你为什么,你想想你该怎么回答,关于这道题我总结了几个为什么,

    • async await 的执行规则是什么样的?

    • await 后面的代码是怎么执行的?

    • await 后面,如果是同步执行的代码,和异步执行的代码,对面代码执行有什么影响?

    • 为什么await后面的代码,会被执行什么操作?

  • 带着问题我们来找答案!(尽量简洁)

    • 函数前面加上async时,当我在这个函数调用的时候进行打印发现它输出的是一个promise对象,其实这个函数的本质就是隐式返回了一个promise对象作为结果的函数
    • 如果你返回的不是一个promise,JavaScript也会自动把这个值"包装"成Promise的resolve值
    • 同时,在单独使用aysnc时,函数里面加上return,和不加上return,返回值也会有差别,果你写了return,那么return的值就会作为你成功的时候传入的值上代码(看输出的Promise{:的值}):

    • 在这个函数里里我们加上await后,也是我们平时项目里面常用的,即使调用的是异步代码,它也会变成类似于同步,只有让这个异步代码执行完后,才会执行下面的同步代码来执行。注意一点不能单独使用await,不然就爆红了。
    • await会“阻塞”后面的函数,也就是console.log('async1 end')(下一行的代码)
    • 那么可以简单理解为,await后面的函数执行完毕时,await会产生一个微任务(也就是为什么会阻塞了)。但是我们要注意这个微任务产生的时机,它是执行完await之后,回调函数会被压入microtask队列,也就是console.log('async1 end'),然后直接跳出async函数,执行其他代码。其他代码执行完毕后,再回到async函数里面去执行剩下的代码
    • 结束,基本上上面题目的的答案也明了了,结合微任务宏任务的执行机制,顶部的题也就做出来了,说的相对简洁,如有不到之处,评论区交流!

结语

  • 再来一题:(答案,copy到当前控制台,看看有没有整对)

    console.log('script start')
    async function async1() {
      await async2()
      console.log('async1 end')
      
    }
    async function async2() {
      console.log('async2 end')
      setTimeout(function() {
      console.log('setTimeout1')
    },0)
    return new Promise(resolve => {
      console.log('promise3')
      resolve()
    }).then(function() {
      console.log('promise4')
    })
    }
    async1()
    
    setTimeout(function() {
      console.log('setTimeout')
    },0)
    
    new Promise(resolve => {
      console.log('promise')
      resolve()
    }).then(function() {
      console.log('promise1')
    }).then(function() {
      console.log('promise2')
    })
    
    console.log('script end')