详解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 相关文章推荐
jquery插件珍藏(图片局部放大/信息提示框)
Jan 08 Javascript
js动态添加删除,后台取数据(示例代码)
Nov 25 Javascript
jquery prop的使用介绍及与attr的区别
Dec 19 Javascript
常规表格多表头查询示例
Feb 21 Javascript
jquery删除ID为sNews的tr元素的内容
Apr 10 Javascript
TypeScript具有的几个不同特质
Apr 07 Javascript
小心!AngularJS结合RequireJS做文件合并压缩的那些坑
Jan 09 Javascript
VUEJS实战之构建基础并渲染出列表(1)
Jun 13 Javascript
简单实现js进度条加载效果
Mar 25 Javascript
vue登录注册及token验证实现代码
Dec 14 Javascript
基于Vuex无法观察到值变化的解决方法
Mar 01 Javascript
vue前端框架—Mint UI详解(更适用于移动端)
Apr 30 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循环语句笔记(foreach,list)
2011/11/29 PHP
PHP小教程之实现链表
2014/06/09 PHP
WordPress中获取页面链接和标题的相关PHP函数用法解析
2015/12/17 PHP
YII Framework框架教程之日志用法详解
2016/03/14 PHP
php构造函数与析构函数
2016/04/23 PHP
php  PATH_SEPARATOR判断当前服务器系统类型实例
2016/10/28 PHP
PHP redis实现超迷你全文检索
2017/03/04 PHP
ThinkPHP 3.2.3实现页面静态化功能的方法详解
2017/08/03 PHP
gearman管理工具GearmanManager的安装与php使用方法示例
2020/02/27 PHP
JavaScript检测实例属性, 原型属性
2015/02/04 Javascript
JavaScript时间转换处理函数
2015/04/14 Javascript
jQuery滚动条插件nanoscroller使用指南
2015/04/21 Javascript
JavaScript中setMonth()方法的使用详解
2015/06/11 Javascript
基于javascript实现图片预加载
2016/01/05 Javascript
基于JavaScript实现全屏透明遮罩div层锁屏效果
2016/01/26 Javascript
bootstrap响应式工具使用详解
2017/11/29 Javascript
vue基础之事件简写、事件对象、冒泡、默认行为、键盘事件实例分析
2019/03/11 Javascript
详解如何更好的使用module vuex
2019/03/27 Javascript
30分钟用Node.js构建一个API服务器的步骤详解
2019/05/24 Javascript
vue实现简单图片上传
2020/06/30 Javascript
详解Vue的组件中data选项为什么必须是函数
2020/08/17 Javascript
[47:04]LGD vs infamous Supermajor小组赛D组 BO3 第二场 6.3
2018/06/04 DOTA
[01:32]dota2拉比克至宝(222)
2018/12/20 DOTA
Python应用03 使用PyQT制作视频播放器实例
2016/12/07 Python
python+pandas分析nginx日志的实例
2018/04/28 Python
Python、 Pycharm、Django安装详细教程(图文)
2019/04/12 Python
python基于paramiko将文件上传到服务器代码实现
2019/07/08 Python
使用Python实现 学生学籍管理系统
2019/11/26 Python
Pandas+Matplotlib 箱式图异常值分析示例
2019/12/09 Python
Python3和pyqt5实现控件数据动态显示方式
2019/12/13 Python
Python 去除字符串中指定字符串
2020/03/05 Python
Blue Nile台湾:钻石珠宝商,订婚首饰、结婚戒指和精品首饰
2017/11/24 全球购物
竞选班干部演讲稿
2014/04/24 职场文书
正风肃纪剖析材料范文
2014/10/10 职场文书
2019教师的学习计划
2019/06/25 职场文书
HTML+VUE分页实现炫酷物联网大屏功能
2021/05/27 Vue.js