从Node7.6开始,默认支持async/await特性。如果你没有使用过,或者不太了解它的用法,本文将告诉你为什么这个功能“不容错过”。本文辅以大量实例。Javascript的异步处理工具,相信大家可以很容易的理解和理解。文章的灵感和内容借鉴自6ReasonsWhyJavaScript'sAsync/AwaitBlowsPromisesAway(Tutorial)。英语好的同学可以直接参考原版。初识Async/await对于不了解Async/await特性的同学,下面一段是“速成”训练。Async/await是一种用Javascript编写异步程序的新方法。以前的异步方法无非就是回调函数和Promise。但是Async/await是建立在Promise之上的。对于Javascript处理异步,是一个老生常谈但永不过时的话题:从最早的回调函数,到Promise对象,再到Generator函数,每一次都有改进,却又让人觉得残缺不全。它们都具有额外的复杂性,并且都需要了解抽象的底层操作机制。异步编程的最高境界就是不关心是不是异步。异步函数是隧道尽头的光明,许多人认为它们是异步操作的最终解决方案。Async/await语法假设我们有一个getJSON方法,它发送对JSON数据的异步请求并返回一个promise对象。这个promise对象的resolve方法传递异步获取的JSON数据。具体例子如下:constmakeRequest=()=>getJSON().then(data=>{console.log(data)return"done"})makeRequest()在使用async/await时写法如下:constmakeRequest=async()=>{console.log(awaitgetJSON())return"done"}makeRequest()比较两种写法,对于第二种,我需要进一步说明:1)第二种写法(使用async/await),async关键字用在main函数之前。在函数体内,使用了await关键字。当然await关键字只能出现在用async声明的函数体中。该函数会隐式返回一个Promise对象,函数体中的返回值将作为Promise对象解析时的参数。可以使用then方法添加回调函数。函数在执行时,一旦遇到await,会先返回,等待异步操作完成,然后在函数体中执行下面的语句。2)例子中,awaitgetJSON()表示在getJSON()返回的promise对象解析后触发console.log调用。我们正在看一个例子来加强我们的理解。本例摘自阮一峰的书《ECMAScript 6 入门》:等待超时(毫秒);console.log(value);}asyncPrint('你好世界',50);上面代码指定50毫秒后,会输出helloworld。Async/await有什么好处?那么,Async/await在处理异步操作时有什么优势呢?我们总结了以下6点。简洁干净。我们看一下上面两段代码的代码量,可以直观的看出使用Async/await节省了很多代码。与Promise相比,我们不需要写.then,我们不需要创建匿名函数来处理响应,我们也不需要将数据赋值给一个我们实际上并不需要的变量。此外,我们避免了耦合的发生。这些看似细小的优点其实非常直观,在后面的代码示例中会被更加放大。错误处理ErrorhandlingAsync/await使得处理同步+异步错误成为现实。我们也使用try/catch结构,但是在promises的情况下,try/catch在JSON.parse的时候很难处理问题,因为错误发生在Promise内部。为了处理这种情况下的错误,我们只能嵌套另一层try/catch,像这样:constmakeRequest=()=>{try{getJSON().then(result=>{//thisparsemayfailconstdata=JSON.parse(result)console.log(data)})//取消注释这个块来处理异步错误//.catch((err)=>{//console.log(err)//})}catch(err){console.log(err)}}然而,如果你使用async/await,一切就变得简单了,解析中的错误也很容易解决:constmakeRequest=async()=>{try{//thisparsemayfailconstdata=JSON.parse(awaitgetJSON())console.log(data)}catch(err){console.log(err)}}条件判别Conditionals想象这样一个业务需求:我们需要先拉取数据,然后判断是根据得到的数据输出数据,还是根据数据内容拉取更多信息。如下:constmakeRequest=()=>{returngetJSON().then(data=>{if(data.needsAnotherRequest){returnmakeAnotherRequest(data).then(moreData=>{console.log(moreData)returnmoreData})}else{console.log(data)returndata}})}这样的代码会让我们头疼。在这么多层(6层)嵌套的过程中,很容易“迷失自我”。使用async/await,我们可以轻松编写更具可读性的代码:console.log(moreData)returnmoreData}else{console.log(data)returndata}}中间值一个常见的场景是我们先调用promise1,然后根据返回值调用promise2,再根据返回值调用promise3这两个Promise的值。使用Promise,我们可以轻松实现:constmakeRequest=()=>{returnpromise1().then(value1=>{//dosomethingreturnpromise2(value1).then(value2=>{//dosomethingreturnpromise3(value1,value2)})})}如果你不能忍受这样的代码,我们可以优化我们的Promise,解决方案是使用Promise.all来避免深度嵌套。像这样:constmakeRequest=()=>{returnpromise1().then(value1=>{//做点什么returnPromise.all([value1,promise2(value1)])}).then(([value1,value2])=>{//dosomethingreturnpromise3(value1,value2)})}Promise.all这个方法为了更好的可读性牺牲了语义。但实际上,将value1&value2放在一个数组中是很“痛苦”的,某种意义上是多余的。同样的场景,使用async/await会很简单:constmakeRequest=async()=>{constvalue1=awaitpromise1()constvalue2=awaitpromise2(value1)returnpromise3(value1,value2)}错误堆栈信息Errorstacksimagine让我们看看我们将许多promise链接起来,一层又一层。紧接着,promise链出了问题,如下:constmakeRequest=()=>{returncallAPromise().then(()=>callAPromise()).then(()=>callAPromise())。然后(()=>callAPromise()).then(()=>callAPromise()).then(()=>{thrownewError("oops");})}makeRequest().catch(err=>{console.log(err);//输出//Error:oopsatcallAPromise.then.then.then.then.then(index.js:8:13)})这个链的错误堆栈信息没有给出哪里出了问题的线索它出现在哪里。更糟糕的是,他误导了开发人员:错误消息中出现的唯一函数名称实际上是无辜的。再来看看async/await的显示:constmakeRequest=async()=>{awaitcallAPromise()awaitcallAPromise()awaitcallAPromise()awaitcallAPromise()awaitcallAPromise()thrownewError("oops");}makeRequest().catch(err=>{console.log(err);//输出//Error:oopsatmakeRequest(index.js:7:9)})也许这种比较对于地方发展阶段大。但是想象一下在服务器端的情况下,实时代码的错误记录,这将很有意义。你肯定会发现上面的错误信息比“错误来自当时的那个……”有用得多。DebuggingDebugging最后一点,也是很重要的一点,使用async/await调试会变得很简单。在返回表达式的箭头函数中,我们不能设置断点,这将导致以下情况:constmakeRequest=()=>{returncallAPromise().then(()=>callAPromise()).then(()=>callAPromise()).then(()=>callAPromise()).then(()=>callAPromise())}我们不能在每一行都设置断点。但是使用async/await时:constmakeRequest=async()=>{awaitcallAPromise()awaitcallAPromise()awaitcallAPromise()awaitcallAPromise()}总结async/await是JavaScript近几年最具革命性的新特性之一一。他让读者意识到使用Promise的一些问题,并提供了自己的解决方案来替代Promise。当然,这个新特性也有一些顾虑,体现在:它让异步代码变得不那么明显,我们终于学会并习惯了使用回调函数或者.then来处理异步。当然,新功能需要时间和成本去学习和体验;回想起来,熟悉C#语言的程序员一定会明白,这些学习成本是完全值得的。快乐编码!PS:作者的Github仓库,欢迎通过代码以各种形式交流。
