美文网首页源码干货
帮你一步步看清async/await和promise的执行顺序

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

作者: 源码时代官方 | 来源:发表于2018-12-29 15:37 被阅读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关键字还有哪些需要注意的?

    • 在语义上要理解,async表示函数内部有异步操作
    • · 另外注意,一般await关键字要在async关键字函数的内部,await写在外面会报错。

    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对象
    • 是promise对象

    如果不是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任务包括:Promise.then、MutationObserver回调、process.nextTick

    也就是说,一段代码会被分为两部分,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已执行完毕,要处理微任务队列中的代码,微任务队列,也要遵循先进先出的原则:

    • 执行微任务1,打印promise1
    • 执行微任务2,没什么内容

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

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

    image

    相关文章

      网友评论

        本文标题:帮你一步步看清async/await和promise的执行顺序

        本文链接:https://www.haomeiwen.com/subject/rtqplqtx.html