随着Node7的发布,越来越多的人开始研究async/await,据说是异步编程的终极解决方案。第一次看到这组关键字并不是在JavaScript语言中,而是在c#5.0的语法中。C#的async/await需要在.NETFramework4.5以上的版本中使用,所以心里还是难过一阵子——为了兼容XP系统,我们开发的软件不能使用.NETFramework4.0以上的版本。我早些时候在《闲谈异步调用“扁平”化》中谈到了这一点。Async/await是一个很棒的特性,无论是在C#还是JavaScript中,它们也是非常甜美的语法糖。C#的async/await实现离不开Task或Task类,JavaScript的async/await实现也离不开Promise。现在抛开C#和.NETFramework,专心研究JavaScript的async/await。async和await是干什么的,随便起一个名字都是有意义的,我们从字面理解吧。async是“异步”的简写,而await可以认为是asyncwait的简写。所以应该很好理解,async用来声明一个函数是异步的,await用来等待一个异步方法完成。此外,还有一个有趣的语法规则,await只能出现在async函数中。那么细心的朋友就会有一个疑问。如果await只能出现在async函数中,那么这个async函数应该怎么调用呢?如果需要通过await调用异步函数,则必须在调用外包装一个异步函数。然后...进入死循环,永远出不来...如果async函数不需要await调用,那async是做什么的呢?async是干什么的这个问题的关键在于async函数是如何处理它的返回值的!我们当然希望它能直接通过return语句返回我们想要的值,但是如果是这样的话,好像也没什么好等待的了。所以,试着写一段代码看看它会返回什么:asyncfunctiontestAsync(){return"helloasync";}constresult=testAsync();console.log(result);当您看到输出时,您会突然恍然大悟——输出是一个Promise对象。c:\var\test>node--harmony_async_await.Promise{'helloasync'}因此,异步函数返回一个Promise对象。此信息也可以从文档中获取。异步函数(包括函数语句、函数表达式和Lambda表达式)将返回一个Promise对象。如果函数中返回的是直接量,async会通过Promise.resolve()将直接量封装成一个Promise对象。async函数返回的是一个Promise对象,所以当最外层不能使用await获取它的返回值时,当然我们应该使用原来的方式:then()链来处理这个Promise对象,比如这个testAsync()。然后(v=>{console.log(v);//输出helloasync});现在想想,如果async函数没有返回值怎么办?很容易想到它会返回Promise.resolve(undefined)。想一想Promise的特点——没有等待,所以如果你执行一个async函数没有await,它会立即执行,返回一个Promise对象,永??远不会阻塞后面的语句。这与返回Promise对象的普通函数没有什么不同。那么接下来的关键点就是await关键字了。await在等什么?通常,await被认为是等待异步函数完成。但是按照语法,await等待一个表达式,这个表达式的结果是一个Promise对象或者其他值(也就是说,没有什么特别的限制)。因为async函数返回的是一个Promise对象,所以可以使用await来等待一个async函数的返回值——这也可以说await是在等待async函数,但要明确一点,它实际上是在等待一个返回值.请注意,await不仅用于等待Promise对象,它还可以等待任何表达式的结果,因此,在await之后,它后面实际上可以进行普通函数调用或直接量。所以下面的例子可以运行functiongetSomething(){return"something";}asyncfunctiontestAsync(){returnPromise.resolve("helloasync");}asyncfunctiontest(){constv1=awaitgetSomething();constv2=awaittestAsync();constv2=awaittestAsync();log(v1,v2);}test();await等待它等待的东西,然后await等待它等待的东西,一个Promise对象,或者其他值,然后呢?不得不说await是一个运算符,用来组成一个表达式,await表达式的结果取决于它等待的是什么。如果它不是Promise,则await表达式计算它正在等待的内容。如果它等待一个Promise对象,await将会很忙。它会阻塞下面的代码,等待Promise对象解析,然后获取解析后的值作为await表达式的结果。看到上面blocking这个词,慌了。。。别着急,这就是async函数中必须使用await的原因。异步函数调用不会造成阻塞,所有内部阻塞都封装在一个Promise对象中异步执行。async/await为我们做了什么?做一个简单的比较。上面已经解释了,async会将后续函数(函数表达式或Lambda)的返回值封装到一个Promise对象中,而await会等待Promise完成,并将其resolve的结果返回。现在例如使用setTimeout来模拟耗时的异步操作。首先,让我们看看如何在不使用async/await的情况下编写functiontakeLongTime(){returnnewPromise(resolve=>{setTimeout(()=>resolve("long_time_value"),1000);});}takeLongTime().then(v=>{console.log("得到",v);});如果你改用async/await,它会像这样functiontakeLongTime(){returnnewPromise(resolve=>{setTimeout(()=>resolve("long_time_value"),1000);});}asyncfunctiontest(){constv=awaittakeLongTime();console.log(v);}test();眼尖的同学发现takeLongTime()没有声明是async的。其实takeLongTime()本身就是返回的Promise对象,不管有没有async,结果都是一样的。不明白的请回头看看上面的“async是干什么的”。另一个问题来了,这两段代码,两种处理异步调用的方式(实际上是Promise对象的处理)区别并不明显,甚至使用async/await需要更多的代码,那么它的优势在哪里呢?在哪里?async/await的优势在于,单一的Promise链在处理then链时无法找到async/await的优势,Promise通过then链解决了多层回调的问题,现在使用async/await进一步优化)。假设一个业务是分多个步骤完成的,每一步都是异步的,依赖于上一步的结果。我们还是用setTimeout来模拟异步操作:/***入参n,表示这个函数的执行时间(毫秒)*执行结果为n+200,下一步会用到这个值*/functiontakeLongTime(n){returnnewPromise(resolve=>{setTimeout(()=>resolve(n+200),n);});}functionstep1(n){console.log(`step1with${n}`);returntakeLongTime(n);}functionstep2(n){console.log(`step2with${n}`);returntakeLongTime(n);}functionstep3(n){console.log(`step3with${n}`);returntakeLongTime(n);}now使用Promise方法实现这三个步骤的处理step3(time3)).then(result=>{console.log(`resultis${result}`);console.timeEnd("doIt");});}doIt();//c:\var\test>node--harmony_async_await.//step1with300//step2with500//step3with700//resultis900//doIt:1507.251ms输出结果为step3()的参数700+200=900。doIt()依次执行三步,共耗时300+500+700=1500毫秒,与console.time()/console.timeEnd()计算结果一致。如果用async/await实现,会是这样:asyncfunctiondoIt(){console.time("doIt");consttime1=300;consttime2=awaitstep1(time1);consttime3=awaitstep2(time2);constresult=awaitstep3(时间3);console.log(`resultis${result}`);console.timeEnd("doIt");}doIt();结果和之前的Promise实现是一样的,但是这段代码是不是看起来清晰多了,几乎和Synchronization代码一样,更爽。现在更改业务需求。还是三步,但是每一步都需要前面每一步的结果。functionstep1(n){console.log(`step1with${n}`);returntakeLongTime(n);}functionstep2(m,n){console.log(`step2with${m}and${n}`);returntakeLongTime(m+n);}functionstep3(k,m,n){console.log(`step3with${k},${m}and${n}`);returntakeLongTime(k+m+n);}这先用async/await写:asyncfunctiondoIt(){console.time("doIt");consttime1=300;consttime2=awaitstep1(time1);consttime3=awaitstep2(time1,time2);constresult=awaitstep3(time1,time2,time3);console.log(`resultis${result}`);console.timeEnd("doIt");}doIt();//c:\var\test>node--harmony_async_await.//step1with300//step2with800=300+500//step3with1800=300+500+1000//resultis2000//doIt:2907.387ms除了执行时间变长了,好像和前面的例子一样!别着急,想想如果把它写成Promise会是什么样子?functiondoIt(){console.time("doIt");consttime1=300;step1(time1).then(time2=>{returnstep2(time1,time2).then(time3=>[time1,time2,time3]);}).then(times=>{const[time1,time2,time3]=times;returnstep3(time1,time2,time3);}).then(result=>{console.log(`resultis${result}`);console.timeEnd("doIt");});}doIt();是不是觉得有点复杂?那一堆参数处理是Promise方案的死穴——参数传递太麻烦了,看着就头晕!到目前为止,你了解async/await了吗?但实际上,还有一些事情没有提到——Promise可能会拒绝,如何处理?如果需要并行处理3个步骤并等待所有结果怎么办?