详解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 相关文章推荐
filemanage功能中用到的common.js
Apr 08 Javascript
extjs DataReader、JsonReader、XmlReader的构造方法
Nov 07 Javascript
jquery实现勾选复选框触发事件给input赋值
Feb 01 Javascript
cocos2dx骨骼动画Armature源码剖析(三)
Sep 08 Javascript
Node.js 文件夹目录结构创建实例代码
Jul 08 Javascript
详解jQuery uploadify文件上传插件的使用方法
Dec 16 Javascript
Bootstrap 模态框实例插件案例分析
Dec 28 Javascript
JavaScript实现同一个页面打开多张图片
Dec 29 Javascript
jquery实现放大镜简洁代码(推荐)
Jun 08 jQuery
使用Vue如何写一个双向数据绑定(面试常见)
Apr 20 Javascript
Vue中在新窗口打开页面及Vue-router的使用
Jun 13 Javascript
Vue CLI2升级至Vue CLI3的方法步骤
May 20 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操作文件方法问答
2007/03/16 PHP
PHP中Header使用的HTTP协议及常用方法小结
2014/11/04 PHP
分享php分页的功能模块
2015/06/16 PHP
PHP正则表达式之捕获组与非捕获组
2015/11/06 PHP
PHP实现动态添加XML中数据的方法
2018/03/30 PHP
让广告代码不再影响你的网页加载速度
2006/07/07 Javascript
Javascript中arguments和arguments.callee的区别浅析
2015/04/24 Javascript
实现React单页应用的方法详解
2016/08/02 Javascript
Websocket协议详解及简单实例代码
2016/12/12 Javascript
jQuery实现验证码功能
2017/03/17 Javascript
JavaScript该如何学习 怎样轻松学习JavaScript
2017/06/12 Javascript
详解react-webpack2-热模块替换[HMR]
2017/08/03 Javascript
利用js编写网页进度条效果
2017/10/08 Javascript
关于redux-saga中take使用方法详解
2018/02/27 Javascript
Vue中this.$router.push参数获取方法
2018/02/27 Javascript
layUI实现前端分页和后端分页
2019/07/27 Javascript
node静态服务器实现静态读取文件或文件夹
2019/12/03 Javascript
jQuery+Ajax+js实现请求json格式数据并渲染到html页面操作示例
2020/06/02 jQuery
浅谈实现在线预览PDF的几种解决办法
2020/08/10 Javascript
跟老齐学Python之编写类之四再论继承
2014/10/11 Python
Python实现的彩票机选器实例
2015/06/17 Python
判断网页编码的方法python版
2016/08/12 Python
Python基础学习之常见的内建函数整理
2017/09/06 Python
K-近邻算法的python实现代码分享
2017/12/09 Python
对pycharm代码整体左移和右移缩进快捷键的介绍
2018/07/16 Python
Python如何调用外部系统命令
2019/08/07 Python
python实现对服务器脚本敏感信息的加密解密功能
2019/08/13 Python
Python Django中间件使用原理及流程分析
2020/06/13 Python
建筑工程技术应届生求职信
2013/11/17 职场文书
毕业生的自我评价
2013/12/30 职场文书
鲜花方阵解说词
2014/02/13 职场文书
模特大赛策划方案
2014/05/28 职场文书
2014年监理工作总结范文
2014/11/17 职场文书
2015年小学实验室工作总结
2015/07/28 职场文书
2016年寒假家长评语
2015/10/10 职场文书
带你学习MySQL执行计划
2021/05/31 MySQL