前言如果你对asyncawait很熟悉,以前对它很陌生(熟悉就是你可能每天都会用到,不熟悉就是一些组合题看不懂),你可能aswell可以边看边练习,总结规律,相信会慢慢明了,有所收获。本文针对每个案例详细描述了代码的执行过程,如有错误请指正。async函数返回值async函数默认会返回一个Promise对象,不管最后一个函数是否有返回值。但是对于具体的返回值情况,实际表现会有所不同,我们分别来看一下。返回值是一个普通值这里的普通值是指基本类型值(Number、String、Boolean等)和非thenable非Promise值asyncfunctionfoo(){return'foo'}foo().then(()=>console.log('a'))Promise.resolve().then(()=>console.log('b')).then(()=>console.log('c'))//outputResult:abc很简单,不出意外就输出了abc,就是async函数执行完成后没有等待。无需等待foo()的执行完成。遇到then时,将console.log('a')放入microtask队列;继续执行Promise.resolve(),遇到then,将console.log('b')放入队列,当前所有同步任务执行完毕;开始执行microtask队列,先取出执行console.log('a')输出a;然后取出执行console.log('b')输出b,遇到则将console.log('c')入队列;最后取出执行console.log('c')输出c,至此microtask队列清空,代码执行结束;thereturnvalueisthenable所谓value是thenable,就是定义了then方法对象可以是字面量对象,也可以是Class实例。classBar{then(resolve){resolve()console.log('then')}}asyncfunctionfoo(){//returnnewBar()return{then(resolve,reject){resolve()console.log('然后')}}}foo().then(()=>console.log('a'))Promise.resolve().then(()=>console.log('b')).then(()=>console.log('c'))//输出结果:为什么thenbac的顺序不一样?如果async函数的返回值是一个thenable,就相当于生成了一个Promise。当foo函数执行完成,Promise的状态发生变化(resolve或reject)后,仍然需要等待一个。foo()返回thenable值并执行then方法,Promise状态改变,执行console.log('then')输出then,等待1thenduration;继续执行Promise.resolve(),遇到thenQueue将console.log('b')放入microtask中,当前同步任务执行完毕;开始执行microtask队列,先取出执行console.log('b')输出b,清空当前microtask队列;此时step1的等待时间到期,console.log('a')放入队列,取出执行输出a;继续第3步,遇到then,将console.log('c')放入队列,取出执行输出c,至此microtask队列清空,代码执行结束;这里如果foo函数返回的thenable方法的状态没有改变,后面的foo().then永远不会执行。异步函数foo(){return{then(resolve,reject){console.log('then')}}}foo().then(()=>console.log('a'))Promise.resolve().then(()=>console.log('b')).then(()=>console.log('c'))//输出结果:thenbcreturnvalueisPromisereturnvalueisPromise,例如新的Promise(resolve=>resolve())和Promise.resolve。异步函数foo(){returnPromise.resolve('foo')}foo().then(()=>console.log('a'))Promise.resolve().then(()=>console.log('b')).then(()=>console.log('c')).then(()=>console.log('d'))//输出结果:bcad可以清楚的看到异步函数执行后,有2个持续时间的延迟。foo()返回Promise值,Promise状态改变,然后等待2;继续执行Promise.resolve(),遇到then,将console.log('b')放入microtask队列,当前同步任务执行完毕;开始执行microtask队列,先取出执行console.log('b')输出b,并清空当前微任务队列;遇到then,将console.log('c')放入队列,取出执行输出c;当第1步等待时间到期,遇到then时将console.log('a')放入队列,取出执行输出a;继续第4步,遇到then时将console.log('d')放入队列,取出并执行Outputd,至此microtask队列清空,代码执行结束;基于以上表现,可以总结出以下规律:await表达式值既然async函数的返回值会影响代码执行顺序,那么await之后的表达式值是否也会影响呢?下面也分以上三种场景进行实验分析。await值是一个通用值。asyncfunctionfoo(){await'foo'console.log('a')}foo().then(()=>console.log('b'))Promise.resolve().then(()=>console.log('c')).then(()=>console.log('d'))//输出结果:acbd可以判断,如果await之后的表达式的值为普通值,则有无需等待那么长的时间。那么,为什么b输出在c之后呢?在await表达式有执行结果后,从await的下一行到函数结束的codex可以看作是放入了microtask队列,相当于Promise.resolve(awaitxxx).then(()=>codex),这里是伪代码,await按时间顺序等同于Promise.prototype.then。await'foo'执行完成后,将console.log('a')加入微任务队列;继续执行同步任务Promise.resolve(),遇到then时将console.log(c)加入microtask队列,当前同步任务执行完毕;然后执行microtask队列中的任务,取出执行console.log('a')输出a;此时foo函数执行完毕,遇到then会入队console.log('b');继续执行microtask队列中的console.log('c')输出c。这时候遇到then,就会入队console.log('d');最后依次执行剩余的microtasks,执行并输出b和d,此时microtask队列清空,代码执行结束;等待值是thenableasyncfunctionfoo(){await{then(resolve){resolve()console.log('then')}}console.log('a')}foo().then(()=>console.log('b'))Promise.resolve().then(()=>console.log('c')).then(()=>console.log('d')).then(()=>console.log('e'))//输出结果thencadbeawait如果eawait后的表达式值是thenable,则需要等待1then时间后才能执行后续代码。foo()执行await是一个thenable,Promise状态发生变化,执行同步代码console.log('then'),输出then,此时等待一个then时长;继续执行同步任务Promise.resolve(),遇到则将console.log('c')添加到microtask队列,当前同步任务执行完毕;开始执行microtask队列,取出并执行console.log('c'),输出c,清空microtask队列;此时Step1等待时间到,入队await后续代码console.log('a');继续第3步,遇到then时入队console.log('d'),然后依次取出console.log('a')')和console.log('d')一起执行,输出ad;执行console.log('d')后,遇到then,将console.log('e')放入队列,取出执行,输出e;确实有点绕,我们把等待时间一个然后作为从进入队列到下一个微任务执行完成并离开队列的时间。比如这里任务c完成了,下一个任务d即将进入队列,被a插入到队列中。等待值是Promiseasyncfunctionfoo(){awaitPromise.resolve('foo')console.log('a')}foo().then(()=>console.log('b'))Promise.resolve().then(()=>console.log('c')).then(()=>console.log('d')).then(()=>console.log('e'))//输出结果acbdeawait如果eawait后面的表达式是Promise,结果和正常值一样,不需要等待then的长度。为什么不像return是Promise的情况那样是2倍呢?原来这是后来版本nodejs优化的结果:去掉了2个microtasks和1个throwawaypromise。具体原因请参考《翻译》Fasterasyncfunctionsandpromises。.对于更早的版本(node11及更早),输出结果为cdaeb,需要等待2then等待次数。foo()执行await是一个Promise,Promise的状态发生变化,此时waitfor2thendurations;继续执行同步任务Promise.resolve(),遇到then时将console.log('c')加入microtask队列,当前同步任务执行完毕;开始执行microtask队列,取出并执行console.log('c'),输出c,清空microtask队列;enqueueconsole.log('d')遇到then,移除并执行,输出d,microtask队列清空;此时步骤1中的等待时间到期,await后续代码console.log('a')入队;继续第4步,遇到then,console.log('e')进入队列,然后取出console.log('a')和console.log('e')依次执行,输出一个和e;执行console.log('a')后,再将console.log('b')放入队列,取出执行,输出b;我们可以总结上面综合await表达式值的结果。我们只从单一的角度来看async的返回值和await表达式的值。下面综合他们两个来分析(统一在node12+环境下)。await一个普通函数首先,await是一个普通函数(非异步函数)functionbaz(){//console.log('baz')//return'baz'//return{//then(resolve){//console.log('baz')//resolve()//}//}returnnewPromise((resolve)=>{console.log('baz')resolve()})}asyncfunctionfoo(){awaitbaz()console.log('a')}foo().then(()=>console.log('b'))Promise.resolve().then(()=>console.log('c')).then(()=>console.log('d')).then(()=>console.log('e'))//awaitbaz函数返回正常值输出结果为bazacbde//awaitbazfunctionreturnisthenable,andtheoutputresultisbazcadbe//awaitbazfunctionreturnisaPromise,输出结果bazacbde与直接await表达式的值输出一致。baz函数的返回是一个正常的值,不会等待then的持续时间;baz函数的返回是thenable并等待一个then持续时间;baz函数的返回是Promise,不会等待then的持续时间;等待一个异步函数,然后将baz函数更改为异步asyncfunctionbaz(){//console.log('baz')//return'baz'//return{//then(resolve){//console.log('baz')//resolve()//}//}returnnewPromise((resolve)=>{console.log('baz')resolve()})}asyncfunctionfoo(){awaitbaz()console.log('a')}foo().then(()=>console.log('b'))Promise.resolve().then(()=>console.log('c')).then(()=>console.log('d')).then(()=>console.log('e'))//awaitbaz函数返回一个普通值输出结果为bazacbde//awaitbazfunctionreturnisthenableoutputresultisbazcadbe//awaitbazfunctionreturnisPromiseoutputTheresultbazcdaeb//node12以下版本的awaitbazfunctionreturnisaPromiseoutputbazcdeab从中我们可以发现等待时间awaitasync函数与asyncbaz函数的返回值一致。asyncbaz函数的返回是一个普通值,不等待then的持续时间;asyncbaz函数的返回是thenable并等待一个then持续时间;asyncbaz函数的返回是Promise等待两个then时长,但是node12以下的版本会等待三个then时长;综合async,await,Promise,then和setTimeout我们结合async,await,Promise,then和setTimeout来看一个话题constasync1=async()=>{console.log('async1')setTimeout(()=>{console.log('timer1')},2000)awaitnewPromise((resolve)=>{console.log('promise1')resolve()})console.log('async1end')returnPromise.resolve('async1success')}console.log('scriptstart')async1().then((res)=>console.log(res))console.log('scriptend')Promise.resolve(1).then(Promise.resolve(2)).catch(3).then((res)=>console.log(res))setTimeout(()=>{console.log('timer2')},1000)思考几分钟,输出结果//scriptstart//async1//promise1//scriptend//async1end//1//async1成功//timer2//timer1执行同步任务输出脚本start和async1,遇到setTimeout时,放入宏任务Queue;继续执行await表达式,执行newPromise输出promise1,Promise状态发生变化,不等待then的时间,将后续代码加入microtask队列;继续执行输出脚本结束,执行Promise.resolve(1),遇到then时将Promise.resolve(2)放入微任务队列;然后往下执行,遇到setTimeout,放入macrotask队列,至此同步任务执行完毕;开始执行microtaskqueue,取出执行step2的后续代码输出async1end,返回一个变化的Promise对象,需要等待2thendurations;继续取出微任务Promise.resolve(2)并执行,状态为resolved,转到then;遇到thenput(res)=>console.log(res)放入microtask队列,然后取出执行输出1,注意:then中的非函数表达式会被执行,默认返回值为前一个Promise的值,then(Promise.resolve(2))会透传上层的1;此时step5等待时间到,将(res)=>console.log(res)放入microtask队列,然后取出执行输出async1success;最后两个定时器分别超时,输出timer2和timer1;如果这种情况稍微修改constasync1=async()=>{console.log('async1')setTimeout(()=>{console.log('timer1')},2000)awaitnewPromise((resolve)=>{console.log('promise1')})console.log('async1end')return'async1success'}console.log('scriptstart')async1().then((res)=>console.log(res))console.log('脚本结束')Promise.resolve(1).then(2).then(Promise.resolve(3)).catch(4).then((res)=>console.log(res))setTimeout(()=>{console.log('timer2')},1000)//输出结果://脚本开始//async1//promise1//scriptend//1//timer2//timer1的具体过程就不一一列举了。从输出结果可以发现,如果await表达式的promise状态没有改变,下面的代码和后面的then永远不会执行的then的执行时间会被加入到microtask队列中执行在上一个函数执行完成并且Promise状态发生变化之后。总结通过以上是基本的asyncawait的使用场景,以及then、Promise和setTimeout的混合使用,大致可以总结出以下规则:async函数的返回值是thenable,会等待1then时间,并且Promise的值会waitfor2await表达式值为thenable,会等待1thenduration,在node12+中值为Promisedoesnotwaitforthenduration,低版本节点等待2thendurations;await一个async函数,async函数的返回值是thenable,它会等待1个thenduration,其值为Promise会在node12+中等待2个thendurations,在低版本节点中等待3个thendurations;如果then是一个非函数,表达式本身会被执行,默认返回的值是前面Promise的值,即透传前面Promise的结果;如果await表达式的Promise的状态没有改变,下面的代码和后面的then将永远不会被执行;以上案例均为实验所得,过程中如有错误请指正。结束~
