源码干货

帮你一步步看清async/await和promise的执行顺序

2018-12-29  本文已影响14人  源码时代官方

主要内容:

  1. 对于async await的理解
  2. 画图一步步看清宏任务、微任务的执行过程

接下来我们测试一下,看看自己有没有必要接着往下看:

这是去年的一条面试题,你是否能够正确说出打印顺序以及执行这一步骤的原因呢?

image

注:因为这是一道前端面试题,所以答案是以浏览器的机制为准,在node平台上运行会有差异。

答案:script start

async1 start

async2

promise1

script end

promise2

async1 end

setTimeout

如果你的运行结果和答案的一直,可以选择跳过这篇文章啦!

对于async await的理解

这部分,主要会讲解3点内容

  1. async做了一件什么事?
  2. await在等什么?
  3. await等到之后,做了一件什么事?
  4. 补充:async、await和promise有哪些优势?
  5. async做了一件什么事?

一句话概括 : 带async关键字的函数,它使得你函数的返回值必定是promise对象

也就是,如果async关键字函数返回的不是promise,会自动用Promise.resolve()包装 ;如果async关键字函数显式地返回promise,那就以你返回的promise为准,一下是一个简单的例子,可以看到async关键字函数和普通函数返回值的区别:

image

所以你看,async函数也没啥了不起的,有看到带有async关键字的函数也不用紧张,你就想它无非就是把return值包装了一下,其他就跟普通函数一样。

关于async关键字还有哪些需要注意的?

2. await在等什么?

一句概括:await等的是右侧表达式的结果,也就是说,右侧如果是函数,name函数的return值就是---表达式的结果;右侧如果是一个'hello'或者什么值,那表达式的结果就是'hello'

image

这里要注意一点,可能大家都知道await会让出线程,阻塞后面的代码,name上面例子中,'async2'和'script start'谁会先打印呢?是从左向右执行,一旦碰到await直接跳出,阻塞async2()的执行?还是从右向左,先执行async2后,发现await关键字,于是让出线程,阻塞代码呢?

实践的结论是,从右向左。先打印async2,后打印script start

3. await等到之后,做了一件什么事情?

那么右侧表达式的结果,就是await要等的东西。

等到之后,对于await来说,分2个情况

如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西作为await表达式的结果。

如果它等到的是一个promise对象,await也会暂停async后面的代码,先执行async外面的同步代码,等着Promise对象fulfilled,然后把resolve的参数作为await表达式的运行结果。

2>画图一步步看清宏任务、微任务的执行过程

我们以考片的经典面试题为例,分析这个例子中的宏任务和微任务。

什么是宏任务和微任务?W3C规定:

...3. Run: Run the selected task

....6. Microtasks: Perform a microtask checkpoint.

7. Update the rendering.

一次时间循环包括:执行tasks,检查Microtasks队列并执行,执行UI渲染(如果需要)

也就是说,一段代码会被分为两部分,tasks部分和Microtasks部分,执行完后,对该段代码内的UI变更进行处理(不包含内部为了执行代码立即进行的重绘),很明显Microtasks就是为了实现异步操作而设计的。

好了,言归正传,继续看我们之前的题:

image

一段代码执行时,会先执行宏观任务中的同步代码,如果执行中遇到setTimeout之类宏任务,那么就把这个setTimeout内部的函数推入【宏任务的队列】中,下一轮宏任务执行时调用。

如果在执行中遇到promise.then( )之类的微任务,就会推入到【当前宏任务的微任务队列中,在本轮宏任务的同步代码执行都完成后,依次执行所有的任务1、2、3】

image

首先,直接打印同步代码 console.log('script start')

image

将setTimeout放入宏任务队列

image

此时我们开始启动async1函数,函数带有async关键字,但是它只是把return值包装成了promise,其他跟普通函数没有什么区别,按照函数内部分执行顺序,我们会先打印console.log('async1 start')

image

接下来我们遇到的就是await async2( ),前文提到await,它先计算出右侧结果,并暂时中断async函数。因此目前就直接打印console.log('async2')

image

被阻塞后,要执行async之外的代码,执行到new Promise(),Promise构造函数是直接调用的同步代码,所以此处console.log('promise1')

image

代码接着运行到promise.then( ),发现这是一个微任务,所以暂时不打印,只是推入当前宏任务的微任务队列中。

注意:这里只是把promise.then( )推入微任务队列中,并没有执行。微任务会在当前宏任务的同步代码执行完毕后,才会依次执行

image

紧接着打印同步代码console.log('script end'),此刻async外的代码终于走结束了,就该回到await表达式那里,执行await Promise.resolve(undefined)了

image

回到async内部,执行await Promise.resolve(undefined),这部分可能不太好理解,如果一个Promise被传递给一个await 操作符,await将等待Promise正常处理完成后并返回其处理结果。此处的await Promise.resolve( )就类似于

Promise.resolve(undefined).then(undefined) => { }

把then执行完,才是await async2()执行结束,await async2()执行结束,才能继续执行后面的代码

image

此时当前的宏任务1已执行完毕,要处理微任务队列中的代码,微任务队列,也要遵循先进先出的原则:

在执行微任务2后,await async2()的语句就彻底结束了,后面的代码不会再阻塞,所以打印console.log('async1 end'),宏观任务1执行完之后,随之执行宏任务2,最后打印了console.log('setTimeout')

不知道经过这么详细的解释之后,你是否看懂了async await和promise的执行顺序呢?提醒小伙伴哦,部分浏览器打印出来的顺序可能会存在一些小的差异性,小伙伴要主动研究查找原因哦!

image
上一篇 下一篇

猜你喜欢

热点阅读