👉本文已更新并重新发布, 请在此处阅读最新版本
从7.6版开始,NodeJS就支持async / await。 我相信自2017年以来,它已成为JS的最大补充。如果您还没有尝试过,则有很多原因,并举例说明了为什么您应该立即采用它,并且永远不要回头。
异步/等待101
对于那些以前从未听说过此主题的人,这里有一个简短的介绍
异步/等待是一种编写异步代码的新方法。 异步代码的替代方案是回调和Promise.Async / await实际上只是在Promise之上构建的语法糖。 它不能与普通回调或节点回调一起使用。异步/等待与承诺一样,是非阻塞的。异步/等待使异步代码的外观和行为更像同步代码。 这就是它所有力量的所在。
句法
假设一个函数getJSON
返回一个promise,并且使用某些JSON对象解析该promise。 我们只想调用它并记录该JSON,然后返回"done".
这就是您使用诺言实现它的方式
const makeRequest = () =>
getJSON()
.then( data => {
console .log(data)
return "done"
})
makeRequest()
这就是async / await的样子:
const makeRequest = async () => {
console .log( await getJSON())
return "done"
}
makeRequest()
这里有一些区别:
1.我们的函数前面有关键字async
。 await
关键字只能在async
定义的函数内使用。 任何异步函数都会隐式返回一个Promise,并且Promise的resolve值将是您从函数返回的值(在本例中为字符串“ done”)。
2.以上几点暗示我们不能在代码的顶层使用await,因为它不在异步函数内。
// this will not work in top level
// await makeRequest()
// this will work
makeRequest().then( ( result ) => {
// do something
})
3. await getJSON()
表示console.log调用将等待,直到getJSON()
承诺解析并打印其值。
为什么会更好?
1.简洁干净
看看我们没有写多少代码! 即使在上面人为设计的示例中,很显然,我们也节省了大量代码。 我们不必编写.then
,也不必创建匿名函数来处理响应,也不必为不需要使用的变量提供名称data
。 我们还避免了嵌套代码。 这些小的优点很快就会加起来,在下面的代码示例中将变得更加明显。
2.错误处理
Async / await最终使您可以使用相同的构造(很好的try/catch
来处理同步和异步错误。 在下面带有promise的示例中,如果JSON.parse
失败,则try/catch
将不会处理,因为它发生在promise中。 我们需要在.catch
上调用.catch
并复制我们的错误处理代码,(希望如此)该代码比在生产就绪代码中的console.log
更复杂。
const makeRequest = () => {
try {
getJSON()
.then( result => {
// this parse may fail
const data = JSON .parse(result)
console .log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console .log(err)
}
现在使用async / await查看相同的代码。 catch
块现在将处理解析错误。
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON .parse( await getJSON())
console .log(data)
} catch (err) {
console .log(err)
}
}
3.有条件的
试想一下像下面的代码这样的代码,它获取一些数据并根据数据中的某些值决定是应返回该数据还是获得更多详细信息。
const makeRequest = () => {
return getJSON()
.then( data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then( moreData => {
console .log(moreData)
return moreData
})
} else {
console .log(data)
return data
}
})
}
只看这个会让你头疼。 很容易迷失所有嵌套(6级),花括号和return语句,而这些嵌套只需要将最终结果传播到主要承诺即可。
当使用async / await重写时,此示例变得更具可读性。
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console .log(moreData)
return moreData
} else {
console .log(data)
return data
}
}
4.中间值
您可能发现自己处于以下情况:调用promise1
,然后使用返回的值调用promise2
,然后使用两个promise的结果调用promise3
。 您的代码很可能看起来像这样
const makeRequest = () => {
return promise1()
.then( value1 => {
// do something
return promise2(value1)
.then( value2 => {
// do something
return promise3(value1, value2)
})
})
}
如果promise3
不需要value1,则可以很容易地将promise嵌套一点。 如果您是那种无法忍受的人,则可以将值1和2都包装在Promise.all
并避免像这样的更深层的嵌套
const makeRequest = () => {
return promise1()
.then( value1 => {
// do something
return Promise .all([value1, promise2(value1)])
})
.then( ( [value1, value2] ) => {
// do something
return promise3(value1, value2)
})
}
为了易于阅读,此方法牺牲了语义。 除了避免嵌套承诺,没有理由让value1
和value2
一起属于数组。
通过异步/等待,相同的逻辑变得非常简单和直观。 它使您想知道您在努力使诺言看起来不那么可怕时可以做的所有事情。
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
5.错误堆栈
想象一下一段代码,它在一个链中调用了多个promise,在该链的某处抛出了一个错误。
const makeRequest = () => {
return callAPromise()
.then( () => callAPromise())
.then( () => callAPromise())
.then( () => callAPromise())
.then( () => callAPromise())
.then( () => {
throw new Error ( "oops" );
})
}
makeRequest()
.catch( err => {
console .log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
从承诺链返回的错误堆栈无法提供错误发生位置的任何线索。 更糟糕的是,这具有误导性。 它包含的唯一函数名称是callAPromise
,它完全没有发生此错误(尽管文件和行号仍然有用)。
但是,来自异步/等待的错误堆栈指向包含错误的函数
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error ( "oops" );
}
makeRequest()
.catch( err => {
console .log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})
当您在本地环境中进行开发并在编辑器中打开文件时,这并不是一个巨大的优势,但是当您试图理解来自生产服务器的错误日志时,这将非常有用。 在这种情况下,知道makeRequest
发生的错误要比知道错误是由a after after then after then来的要好。
6.调试
最后但并非最不重要的一点是,使用异步/等待时的杀手级优势是调试起来容易得多。 调试Promise总是很痛苦,原因有两个
1.您不能在返回表达式的箭头函数中设置断点(无正文)。
![](https://i-blog.csdnimg.cn/blog_migrate/e97363bdde663c75ea35261564028e85.png)
尝试在此处的任何地方设置断点
2.如果在.then
块内设置断点并使用诸如step-over的调试快捷方式,则调试器将不会移至以下.then
因为它仅“步进”通过同步代码。
使用async / await,您不需要那么多的箭头功能,并且可以完全像正常的同步调用一样逐步执行await调用。
![](https://i-blog.csdnimg.cn/blog_migrate/f454af2e61ded59e3d36bb01dbe6f5d2.png)
结论
异步/等待是过去几年中已添加到JavaScript中的最具革命性的功能之一。 它使您意识到什么是句法混乱,并提供了直观的替代方法。
顾虑
您可能会对使用此功能有一些怀疑,那就是它使异步代码变得不那么明显:我们的眼睛学会了在看到回调或.NET时发现异步代码,然后,您的眼睛会花几周的时间来适应新功能。迹象,但是C#拥有此功能已有多年了,熟悉它的人都知道,这种轻微的暂时性不便值得。
在Twitter上关注我@imgaafa r