作为提醒,Node从7.6版开始支持async/await。如果您还没有尝试过,这里有很多原因和示例,说明为什么您应该立即采用它并且永远不要回头。gist中嵌入的代码似乎无法在mediumnativeapp中运行,但它可以在移动浏览器中运行。如果您在应用程序中阅读此内容,请点击共享图标并选择“在浏览器中打开”以查看代码片段。Async/await101对于那些从未听说过该主题的人,这里有一个简短的介绍:Async/await是一种编写异步代码的新方法。以前的异步代码解决方案是回调和承诺。Async/await实际上是建立在promises之上的。它不能与普通回调或节点回调一起使用。Async/await和promises一样,也是非阻塞的。Async/await使异步代码看起来和行为更像同步代码。这就是它的力量所在。该语法假定函数getJSON返回一个承诺,其履行值是某个JSON对象。我们只想调用它,输出那个JSON,然后返回“完成”。这是使用promises实现的代码:constmakeRequest=()=>getJSON().then(data=>{console.log(data)return"done"})makeRequest()这就是使用async/await的样子:constmakeRequest=async()=>{console.log(awaitgetJSON())return"done"}makeRequest()这里有一些区别:1.函数前面有一个关键字async。await关键字仅在使用async定义的函数内部使用。所有异步函数都隐式返回一个承诺,承诺的实现值将是函数的返回值(在本例中为“完成”)。2.以上几点暗示我们不能在代码的顶层使用await,因为它不在async函数中。//这段代码不能在顶层执行//awaitmakeRequest()//这段代码可以执行makeRequest().then((result)=>{//dosomething})3.awaitgetJSON()表示调用console.log将等到getJSON()承诺履行并打印出它的价值。为什么Async/await更好?1.简洁干净看看我们写的代码少了多少!即使在上面人为设计的示例中,很明显我们节省了大量代码。我们不必编写.then,创建匿名函数来处理响应,或为我们不需要的变量提供名称数据。我们还避免了代码嵌套。这些小的优势会迅速累积起来,并在稍后的代码中变得更加明显。2.错误处理Async/await最终会让我们使用同一个结构(try/catch)来处理同步和异步代码成为可能。在下面使用promise的示例中,如果JSON.parse失败,try/catch不会处理它,因为它发生在promise内部。我们需要对promise调用.catch并重复错误处理代码。此错误处理代码比生产就绪代码中的console.log更复杂。constmakeRequest=()=>{try{getJSON().then(result=>{//thisparsemayfailconstdata=JSON.parse(result)console.log(data)})//取消注释thisblocktohandleeasysynchronouserrors//.catch((err)=>{//console.log(err)//})}catch(err){console.log(err)}}现在看看用async/await实现的代码。catch块现在处理解析错误。constmakeRequest=async()=>{try{//这个解析会失败constdata=JSON.parse(awaitgetJSON())console.log(data)}catch(err){console.log(err)}}3.条件句假设您想像下面的代码那样做一些事情,获取一些数据,并决定是否应该返回该数据,或者根据数据中的某些值获取更多详细信息。constmakeRequest=()=>{returngetJSON().then(data=>{if(data.needsAnotherRequest){returnmakeAnotherRequest(data).then(moreData=>{console.log(moreData)returnmoreData})}else{console.log(data)returndata}})}这些代码看着就头疼。它只是将最终结果传播到主要承诺,但很容易迷失在嵌套(6层)、花括号和返回语句中。重写此示例以使用async/await使其更易于阅读。onstmakeRequest=async()=>{constdata=awaitgetJSON()if(data.needsAnotherRequest){constmoreData=awaitmakeAnotherRequest(data);console.log(moreData)returnmoreData}else{console.log(data)returndata}}4.中间值您可能会发现自己处于调用promise1的状态,然后使用其返回值调用promise2,然后使用两个promise的结果调用promise3。您的代码可能如下所示:constmakeRequest=()=>{returnpromise1().then(value1=>{//dosomethingreturnpromise2(value1).then(value2=>{//dosomethingreturnpromise3(value1,value2)})})如果promise3不需要value1,则很容易将promise的嵌套扁平化。如果你是受不了的类型,或许可以这样,将值1和2包裹在一个Promise.all中,避免进一步嵌套:onstmakeRequest=()=>{returnpromise1().then(value1=>{//dosomethingreturnPromise.all([value1,promise2(value1)])}).then(([value1,value2])=>{//dosomethingreturnpromise3(value1,value2)})}这种语义方法被牺牲了可读性。除了避免promise嵌套之外,没有理由将value1和value2组合成一个数组。但是使用async/await,相同的逻辑变得超级简单和直观。当你拼命想让承诺看起来不那么可怕时,它会让你质疑你所做的一切。constmakeRequest=async()=>{constvalue1=awaitpromise1()constvalue2=awaitpromise2(value1)returnpromise3(value1,value2)}5.错误堆栈如果链中有调用多个promise的代码,会被抛到某处在链中出错。constmakeRequest=()=>{returncallAPromise().then(()=>callAPromise()).then(()=>callAPromise()).then(()=>callAPromise()).then(()=>callAPromise()).then(()=>{thrownewError("oops");})}makeRequest().catch(err=>{console.log(err);//输出//错误:oopsatcallAPromise.then.then.then.then.then(index.js:8:13)})从promise链返回的错误堆栈没有提供关于错误发生位置的线索。更糟糕的是,它具有误导性;它包含的唯一函数名称是callAPromise,它与此错误完全无关(尽管文件和行号仍然有用)。但是,来自async/await的错误堆栈将指向包含错误的函数:().catch(err=>{console.log(err);//output//Error:oopsatmakeRequest(index.js:7:9)})在本地环境开发,在编辑器中打开文件时,这是没什么大不了的,但是在尝试从生产服务器中找出错误日志时非常有用。在这种情况下,最好知道错误发生在makeRequest中,而不是知道错误来自一个接一个。6.调试***但同样重要的是,当使用async/await时,一个杀手级优势是调试更容易。调试promises如此痛苦有两个原因:1.无法在返回表达式(无函数体)的箭头函数中设置断点。尝试在此处设置断点2.如果您在.then块中设置断点并使用调试快捷方式(如步入),调试器将不会移动到以下.then因为它仅步进同步代码。使用async/await,我们不再需要那么多箭头函数,您可以像普通的同步调用一样单步执行await调用。总结Async/await是过去几年添加到JavaScript中的最具革命性的特性之一。它让我们意识到promises的语法有多么混乱,并提供了一个直观的替代方案。
