详解Node.js中的Async和Await函数


Posted in Javascript onFebruary 22, 2018

在本文中,你将学习如何使用Node.js中的async函数(async/await)来简化callback或Promise.

异步语言结构在其他语言中已经存在了,像c#的async/await、Kotlin的coroutines、go的goroutines,随着Node.js 8的发布,期待已久的async函数也在其中默认实现了。

Node中的async函数是什么?

当函数声明为一个Async函数它会返回一个 AsyncFunction 对象,它们类似于 Generator 因为执可以被暂停。唯一的区别是它们返回的是 Promise 而不是 { value: any, done: Boolean } 对象。不过它们还是非常相似,你可以使用 co 包来获取同样的功能。

在async函数中,可以等待 Promise 完成或捕获它拒绝的原因。

如果你要在Promise中实现一些自己的逻辑的话

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 让这个代码看起来像同步执行的代码

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)
})

async函数模式

在处理异步操作时,有很多例子让他们就像处理同步代码一样。如果使用 Promise 或 callbacks 来解决问题时需要使用很复杂的模式或者外部库。

当需要再循环中使用异步获取数据或使用 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 完成后的值。

方案1: then 圣诞树

function executeAsyncTask () {
 return functionA()
 .then((valueA) => {
  return functionB(valueA)
  .then((valueB) => {   
   return functionC(valueA, valueB)
  })
 })
}

用这个解决方案,我们在第三个 then 中可以获得 valueA 和 valueB ,然后可以向前面两个 then 一样获得 valueA 和 valueB 的值。这里不能将圣诞树(毁掉地狱)拉平,如果这样做的话会丢失闭包, valueA 在 functioinC 中将不可用。

方案2:移动到上一级作用域

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 来保存相同的值。

方案3:使用一个多余的数组

function executeAsyncTask () {
 return functionA()
 .then(valueA => {
  return Promise.all([valueA, functionB(valueA)])
 })
 .then(([valueA, valueB]) => {
  return functionC(valueA, valueB)
 })
}

在函数 functionA 的 then 中使用一个数组将 valueA 和 Promise 一起返回,这样能有效的扁平化圣诞树(回调地狱)。

方案4:写一个帮助函数

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函数,虽然它们看起来不是很直观,但是你可以在控制台中实验以下代码。

1.map

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))

2.filter

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))

3.reduce

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 中

重写基于callback的node应用成

Async 函数默认返回一个 Promise ,所以你可以使用 Promises 来重写任何基于 callback 的函数,然后 await 等待他们执行完毕。在node中也可以使用 util.promisify 函数将基于回调的函数转换为基于 Promise 的函数

重写基于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+的版本不妨尝试一下,或许会有新的收获。

总结

以上所述是小编给大家介绍的Node.js中的Async和Await函数,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
出现“不能执行已释放的Script代码”错误的原因及解决办法
Aug 29 Javascript
javascript+mapbar实现地图定位
Apr 09 Javascript
Javascript之旅 对象的原型链之由来
Aug 25 Javascript
兼容ie、firefox的图片自动缩放的css跟js代码分享
Jan 21 Javascript
浅谈javascript语法和定时函数
May 03 Javascript
js预加载图片方法汇总
Jun 15 Javascript
微信小程序 选择器(时间,日期,地区)实例详解
Nov 16 Javascript
ES6中Iterator与for..of..遍历用法分析
Mar 31 Javascript
Node.js  REPL (交互式解释器)实例详解
Aug 06 Javascript
使用JavaScript中的lodash编写双色球效果
Jun 24 Javascript
简化版的vue-router实现思路详解
Oct 19 Javascript
JS面向对象编程实现的拖拽功能案例详解
Mar 03 Javascript
解决webpack无法通过IP地址访问localhost的问题
Feb 22 #Javascript
webpack-dev-server远程访问配置方法
Feb 22 #Javascript
关于Webpack dev server热加载失败的解决方法
Feb 22 #Javascript
基于webpack-hot-middleware热加载相关错误的解决方法
Feb 22 #Javascript
解决Webpack 热部署检测不到文件变化的问题
Feb 22 #Javascript
webpack-dev-server自动更新页面方法
Feb 22 #Javascript
javascript高仿热血传奇游戏实现代码
Feb 22 #Javascript
You might like
PHP通过API获取手机号码归属地
2015/05/28 PHP
如何通过Linux命令行使用和运行PHP脚本
2015/07/29 PHP
详解PHP函数 strip_tags 处理字符串缺陷bug
2017/06/11 PHP
js获取客户端网卡的IP地址、MAC地址
2014/03/26 Javascript
jquery实现图片按比例缩放示例
2014/07/01 Javascript
jQuery获取DOM节点实例分析(2种方式)
2015/12/15 Javascript
JavaScript控制浏览器全屏及各种浏览器全屏模式的方法、属性和事件
2015/12/20 Javascript
AngularJS 中文API参考手册
2016/07/28 Javascript
AngularJS实现按钮提示与点击变色效果
2016/09/07 Javascript
微信小程序  modal弹框组件详解
2016/10/27 Javascript
wap手机端解决返回上一页的js实例
2016/12/08 Javascript
通过jsonp获取json数据实现AJAX跨域请求
2017/01/22 Javascript
面试常见的js算法题
2017/03/23 Javascript
node+vue实现用户注册和头像上传的实例代码
2017/07/20 Javascript
vue实现留言板todolist功能
2017/08/16 Javascript
解决vue的 v-for 循环中图片加载路径问题
2018/09/03 Javascript
jQuery实现获取及设置CSS样式操作详解
2018/09/05 jQuery
vuex提交state&amp;&amp;实时监听state数据的改变方法
2018/09/16 Javascript
详解Django通用视图中的函数包装
2015/07/21 Python
TensorFlow实现创建分类器
2018/02/06 Python
Dlib+OpenCV深度学习人脸识别的方法示例
2019/05/14 Python
python实现复制大量文件功能
2019/08/31 Python
一文弄懂Pytorch的DataLoader, DataSet, Sampler之间的关系
2020/07/03 Python
Python Tricks 使用 pywinrm 远程控制 Windows 主机的方法
2020/07/21 Python
编写python代码实现简单抽奖器
2020/10/20 Python
驴妈妈旅游网:中国新型的B2C旅游电子商务网站
2016/08/16 全球购物
西班牙床垫网上商店:Colchones.es
2018/05/06 全球购物
医药营销专业个人自荐信
2013/09/29 职场文书
上班早退检讨书
2014/01/09 职场文书
纺织工程专业个人求职信范文
2014/01/27 职场文书
工会趣味活动方案
2014/08/18 职场文书
毕业论文指导教师评语
2014/12/30 职场文书
2016关于读书活动的心得体会
2016/01/14 职场文书
2016领导干部廉洁从政心得体会
2016/01/19 职场文书
JavaScript canvas实现流星特效
2021/05/20 Javascript
详解PHP Swoole与TCP三次握手
2021/05/27 PHP