在本文中,你将学习如何使用Node.js中的async函数(async/await)来简化callback或Promise.javascript
异步语言结构在其余语言中已经存在了,像c#的async/await、Kotlin的coroutines、go的goroutines,随着Node.js 8的发布,期待已久的async函数也在其中默认实现了。java
当函数声明为一个Async函数它会返回一个AsyncFunction
对象,它们相似于Generator
由于执能够被暂停。惟一的区别是它们返回的是Promise
而不是{ value: any, done: Boolean }
对象。不过它们仍是很是类似,你可使用co包来获取一样的功能。node
在async函数中,能够等待Promise
完成或捕获它拒绝的缘由。git
若是你要在Promise中实现一些本身的逻辑的话github
function handler (req, res) {
return request('https://user-handler-service')
.catch((err) => {
logger.error('Http error', err)
error.logged = true
throw err
})
.then((response) => Mongo.findOne({ user: response.body.user }))
.catch((err) => {
!error.logged && logger.error('Mongo error', err)
error.logged = true
throw err
})
.then((document) => executeLogic(req, res, document))
.catch((err) => {
!error.logged && console.error(err)
res.status(500).send()
})
}
复制代码
可使用async/await
让这个代码看起来像同步执行的代码c#
async function handler (req, res) {
let response
try {
response = await request('https://user-handler-service')
} catch (err) {
logger.error('Http error', err)
return res.status(500).send()
}
let document
try {
document = await Mongo.findOne({ user: response.body.user })
} catch (err) {
logger.error('Mongo error', err)
return res.status(500).send()
}
executeLogic(document, req, res)
}
复制代码
在老的v8版本中,若是有有个promise
的拒绝没有被处理你会获得一个警告,能够不用建立一个拒绝错误监听函数。然而,建议在这种状况下退出你的应用程序。由于当你不处理错误时,应用程序处于一个未知的状态。数组
process.on('unhandledRejection', (err) => {
console.error(err)
process.exit(1)
})
复制代码
在处理异步操做时,有不少例子让他们就像处理同步代码同样。若是使用Promise
或callbacks
来解决问题时须要使用很复杂的模式或者外部库。promise
当须要再循环中使用异步获取数据或使用if-else
条件时就是一种很复杂的状况。闭包
使用Promise
实现回退逻辑至关笨拙异步
function requestWithRetry (url, retryCount) {
if (retryCount) {
return new Promise((resolve, reject) => {
const timeout = Math.pow(2, retryCount)
setTimeout(() => {
console.log('Waiting', timeout, 'ms')
_requestWithRetry(url, retryCount)
.then(resolve)
.catch(reject)
}, timeout)
})
} else {
return _requestWithRetry(url, 0)
}
}
function _requestWithRetry (url, retryCount) {
return request(url, retryCount)
.catch((err) => {
if (err.statusCode && err.statusCode >= 500) {
console.log('Retrying', err.message, retryCount)
return requestWithRetry(url, ++retryCount)
}
throw err
})
}
requestWithRetry('http://localhost:3000')
.then((res) => {
console.log(res)
})
.catch(err => {
console.error(err)
})
复制代码
代码看的让人很头疼,你也不会想看这样的代码。咱们可使用async/await从新这个例子,使其更简单
function wait (timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, timeout)
})
}
async function requestWithRetry (url) {
const MAX_RETRIES = 10
for (let i = 0; i <= MAX_RETRIES; i++) {
try {
return await request(url)
} catch (err) {
const timeout = Math.pow(2, i)
console.log('Waiting', timeout, 'ms')
await wait(timeout)
console.log('Retrying', err.message, i)
}
}
}
复制代码
上面代码看起来很舒服对不对
不像前面的例子那么吓人,若是你有3个异步函数依次相互依赖的状况,那么你必须从几个难看的解决方案中进行选择。
functionA
返回一个Promise
,那么functionB
须要这个值而functioinC
须要functionA
和functionB
完成后的值。
then
圣诞树function executeAsyncTask () {
return functionA()
.then((valueA) => {
return functionB(valueA)
.then((valueB) => {
return functionC(valueA, valueB)
})
})
}
复制代码
用这个解决方案,咱们在第三个then
中能够得到valueA
和valueB
,而后能够向前面两个then
同样得到valueA
和valueB
的值。这里不能将圣诞树(毁掉地狱)拉平,若是这样作的话会丢失闭包,valueA
在functioinC
中将不可用。
function executeAsyncTask () {
let valueA
return functionA()
.then((v) => {
valueA = v
return functionB(valueA)
})
.then((valueB) => {
return functionC(valueA, valueB)
})
}
复制代码
在这颗圣诞树中,咱们使用更高的做用域保变量valueA
,由于valueA
做用域在全部的then
做用域外面,因此functionC
能够拿到第一个functionA
完成的值。
这是一个颇有效扁平化.then
链"正确"的语法,然而,这种方法咱们须要使用两个变量valueA
和v
来保存相同的值。
function executeAsyncTask () {
return functionA()
.then(valueA => {
return Promise.all([valueA, functionB(valueA)])
})
.then(([valueA, valueB]) => {
return functionC(valueA, valueB)
})
}
复制代码
在函数functionA
的then
中使用一个数组将valueA
和Promise
一块儿返回,这样能有效的扁平化圣诞树(回调地狱)。
const converge = (...promises) => (...args) => {
let [head, ...tail] = promises
if (tail.length) {
return head(...args)
.then((value) => converge(...tail)(...args.concat([value])))
} else {
return head(...args)
}
}
functionA(2)
.then((valueA) => converge(functionB, functionC)(valueA))
复制代码
这样是可行的,写一个帮助函数来屏蔽上下文变量声明。可是这样的代码很是不利于阅读,对于不熟悉这些魔法的人就更难了。
async/await
咱们的问题神奇般的消失async function executeAsyncTask () {
const valueA = await functionA()
const valueB = await functionB(valueA)
return function3(valueA, valueB)
}
复制代码
async/await
处理多个平行请求和上面一个差很少,若是你想一次执行多个异步任务,而后在不一样的地方使用它们的值可使用async/await
轻松搞定。
async function executeParallelAsyncTasks () {
const [ valueA, valueB, valueC ] = await Promise.all([ functionA(), functionB(), functionC() ])
doSomethingWith(valueA)
doSomethingElseWith(valueB)
doAnotherThingWith(valueC)
}
复制代码
你能够在map
、filter
、reduce
方法中使用async函数,虽然它们看起来不是很直观,可是你能够在控制台中实验如下代码。
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].map(async (value) => {
const v = await asyncThing(value)
return v * 2
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
复制代码
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].filter(async (value) => {
const v = await asyncThing(value)
return v % 2 === 0
})
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
复制代码
function asyncThing (value) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(value), 100)
})
}
async function main () {
return [1,2,3,4].reduce(async (acc, value) => {
return await acc + await asyncThing(value)
}, Promise.resolve(0))
}
main()
.then(v => console.log(v))
.catch(err => console.error(err))
复制代码
[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
[ 1, 2, 3, 4 ]
10
若是是map迭代数据你会看到返回值为[ 2, 4, 6, 8 ]
,惟一的问题是每一个值被AsyncFunction
函数包裹在了一个Promise
中
因此若是想要得到它们的值,须要将数组传递给Promise.All()
来解开Promise
的包裹。
main()
.then(v => Promise.all(v))
.then(v => console.log(v))
.catch(err => console.error(err))
复制代码
一开始你会等待Promise
解决,而后使用map遍历每一个值
function main () {
return Promise.all([1,2,3,4].map((value) => asyncThing(value)))
}
main()
.then(values => values.map((value) => value * 2))
.then(v => console.log(v))
.catch(err => console.error(err))
复制代码
这样好像更简单一些?
若是在你的迭代器中若是你有一个长时间运行的同步逻辑和另外一个长时间运行的异步任务,async/await版本任然常有用
这种方式当你能拿到第一个值,就能够开始作一些计算,而没必要等到全部Promise
完成才运行你的计算。尽管结果包裹在Promise
中,可是若是按顺序执行结果会更快。
filter
的问题你可能发觉了,即便上面filter函数里面返回了[ false, true, false, true ]
,await asyncThing(value)
会返回一个promise
那么你确定会获得一个原始的值。你能够在return以前等待全部异步完成,在进行过滤。
Reducing很简单,有一点须要注意的就是须要将初始值包裹在Promise.resolve
中
Async
函数默认返回一个Promise
,因此你可使用Promises
来重写任何基于callback
的函数,而后await
等待他们执行完毕。在node中也可使用util.promisify
函数将基于回调的函数转换为基于Promise
的函数
要转换很简单,.then
将Promise执行流串了起来。如今你能够直接使用`async/await。
function asyncTask () {
return functionA()
.then((valueA) => functionB(valueA))
.then((valueB) => functionC(valueB))
.then((valueC) => functionD(valueC))
.catch((err) => logger.error(err))
}
复制代码
转换后
async function asyncTask () {
try {
const valueA = await functionA()
const valueB = await functionB(valueA)
const valueC = await functionC(valueB)
return await functionD(valueC)
} catch (err) {
logger.error(err)
}
}
Rewriting Nod
复制代码
使用Async/Await
将很大程度上的使应用程序具备高可读性,下降应用程序的处理复杂度(如:错误捕获),若是你也使用 node v8+的版本不妨尝试一下,或许会有新的收获。
若有错误麻烦留言告诉我进行改正,谢谢阅读