在 Node.js 中使用 async 函数的方法


Posted in Javascript onNovember 17, 2017

借助于新版 V8 引擎,Node.js 从 7.6 开始支持 async 函数特性。今年 10 月 31 日,Node.js 8 也开始成为新的长期支持版本,因此你完全可以放心大胆地在你的代码中使用 async 函数了。在这边文章里,我会简要地介绍一下什么是 async 函数,以及它会如何改变我们编写 Node.js 应用的方式。

1 什么是 async 函数

利用 async 函数,你可以把基于 Promise 的异步代码写得就像同步代码一样。一旦你使用 async 关键字来定义了一个函数,那你就可以在这个函数内使用 await 关键字。当一个 async 函数被调用时,它会返回一个 Promise。当这个 async 函数返回一个值时,那个 Promise 就会被实现;而如果函数中抛出一个错误,那么 Promise 就会被拒绝。

await 关键字可以被用来等待一个 Promise 被解决并返回其实现的值。如果传给 await 的值不是一个 Promise,那它会把这个值转化为一个已解决的 Promise。

const rp = require('request-promise')
async function main () {
 const result = await rp('https://google.com')
 const twenty = await 20
 // 睡个1秒钟
 await new Promise (resolve => {
  setTimeout(resolve, 1000)
 })
 return result
}
main()
 .then(console.log)
 .catch(console.error)

2 向 async 函数迁移

如果你的 Node.js 应用已经在使用Promise,那你只需要把原先的链式调用改写为对你的这些 Promise 进行 await。

如果你的应用还在使用回调函数,那你应该以渐进的方式转向使用 async 函数。你可以在开发一些新功能的时候使用这项新技术。当你必须调用一些旧有的代码时,你可以简单地把它们包裹成为 Promise 再用新的方式调用。

要做到这一点,你可以使用内建的 util.promisify方法:

const util = require('util')
const {readFile} = require('fs')
const readFileAsync = util.promisify(readFile)
async function main () {
 const result = await readFileAsync('.gitignore')
 return result
}
main()
 .then(console.log)
 .catch(console.error)

3 Async 函数的最佳实践

3.1 在 express 中使用 async 函数

express 本来就支持 Promise,所以在 express 中使用 async 函数是比较简单的:

const express = require('express')
const app = express()
app.get('/', async (request, response) => {
 // 在这里等待 Promise
 // 如果你只是在等待一个单独的 Promise,你其实可以直接将将它作为返回值返回,不需要使用 await 去等待。
 const result = await getContent()
 response.send(result)
})
app.listen(process.env.PORT)

但正如 Keith Smith 所指出的,上面这个例子有一个严重的问题——如果 Promise 最终被拒绝,由于这里没有进行错误处理,那这个 express 路由处理器就会被挂起。

为了修正这个问题,你应该把你的异步处理器包裹在一个对错误进行处理的函数中:

const awaitHandlerFactory = (middleware) => {
 return async (req, res, next) => {
  try {
   await middleware(req, res, next)
  } catch (err) {
   next(err)
  }
 }
}
// 然后这样使用:
app.get('/', awaitHandlerFactory(async (request, response) => {
 const result = await getContent()
 response.send(result)
}))

3.2 并行执行

比如说你正在编写这样一个程序,一个操作需要两个输入,其中一个来自于数据库,另一个则来自于一个外部服务:

async function main () {
 const user = await Users.fetch(userId)
 const product = await Products.fetch(productId)
 await makePurchase(user, product)
}

在这个例子中,会发生什么呢?

你的代码会首先去获取 user,

然后获取 product,

最后再进行支付。

如你所见,由于前两步之间并没有相互依赖关系,其实你完全可以将它们并行执行。这里,你应该使用 Promise.all 方法:

async function main () {
 const [user, product] = await Promise.all([
  Users.fetch(userId),
  Products.fetch(productId)
 ])
 await makePurchase(user, product)
}

而有时候,你只需要其中最快被解决的 Promise 的返回值——这时,你可以使用 Promise.race 方法。

3.3 错误处理

考虑下面这个例子:

async function main () {
 await new Promise((resolve, reject) => {
  reject(new Error('error'))
 })
}
main()
 .then(console.log)

当执行这段代码的时候,你会看到类似这样的信息:

(node:69738) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: error

(node:69738) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

在较新的 Node.js 版本中,如果 Promise 被拒绝且未得到处理,整个 Node.js 进程就会被中断。因此必要的时候你应该使用 try-catch:

const util = require('util')
async function main () {
 try {
  await new Promise((resolve, reject) => {
   reject(new Error('?'))
  })
 } catch (err) {
  // 在这里处理错误
  // 根据你的需要,有时候把错误直接再抛出也是可行的
 }
}
main()
 .then(console.log)
 .catch(console.error)

可是,使用 try-catch 可能会隐藏掉一些重要的异常,比如像系统错误,你可能更想把它再抛出来。关于在什么情况下你应该将错误再次抛出,我强烈建议你去读一下 Eran 的这篇文章。

3.4 更为复杂的流程控制

Caolan McMahon 的 async 是一个出现较早的用于 Node.js 中异步流程控制的库。它提供了一些进行异步操作控制的帮助工具,比如:

mapLimit,
filterLimit,
concatLimit,

以及 priorityQueue。

如果你不打算重新发明轮子,不想把同样的逻辑自己再实现一遍,并且愿意信赖这个经过实践检验的、每月下载量高达 5000 万的库,你可以结合 util.promisify 简单地重用这些函数:

const util = require('util')
const async = require('async')
const numbers = [
 1, 2, 3, 4, 5
]
mapLimitAsync = util.promisify(async.mapLimit)
async function main () {
 return await mapLimitAsync(numbers, 2, (number, done) => {
  setTimeout(function () {
   done(null, number * 2)
  }, 100)
 })
}
main()
 .then(console.log)
 .catch(console.error)

总结

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

Javascript 相关文章推荐
js跨域和ajax 跨域问题的实现思路
Sep 05 Javascript
jquery1.4.2 for Visual studio 2010 模板文件
Jul 14 Javascript
JS 删除字符串最后一个字符的实现代码
Feb 20 Javascript
Javascript中的 “&” 和 “|” 详解
Feb 02 Javascript
Node.js调试技术总结分享
Mar 12 Javascript
ReactNative中使用Redux架构总结
Dec 15 Javascript
微信小程序自定义对话框弹出和隐藏动画
Jul 19 Javascript
vue实现element-ui对话框可拖拽功能
Aug 17 Javascript
js中int和string数据类型互相转化实例
Jan 16 Javascript
javascript中floor使用方法总结
Feb 02 Javascript
如何实现小程序tab栏下划线动画效果
May 18 Javascript
原生js实现随机点名功能
Nov 05 Javascript
JS实现的数组去除重复数据算法小结
Nov 17 #Javascript
bootstrap轮播模板使用方法详解
Nov 17 #Javascript
浅谈Node模块系统及其模式
Nov 17 #Javascript
基于Bootstrap表单验证功能
Nov 17 #Javascript
js 毫秒转天时分秒的实例
Nov 17 #Javascript
Bootstrap table使用方法汇总
Nov 17 #Javascript
Vue.js与 ASP.NET Core 服务端渲染功能整合
Nov 16 #Javascript
You might like
PHP中让curl支持sock5的代码实例
2015/01/21 PHP
php结合安卓客户端实现查询交互实例
2015/05/05 PHP
php实现比较两个文件夹异同的方法
2015/06/18 PHP
CI框架简单邮件发送类实例
2016/05/18 PHP
解析jQuery与其它js(Prototype)库兼容共存
2013/07/04 Javascript
JS返回上一页实例代码通过图片和按钮分别实现
2013/08/16 Javascript
图片动画横条广告带上下滚动可自定义图片、链接等等
2013/10/20 Javascript
javascript模拟php函数in_array
2015/04/27 Javascript
JQUERY简单按钮轮换选中效果实现方法
2015/05/07 Javascript
jQuery往textarea中光标所在位置插入文本的方法
2015/06/26 Javascript
在AngularJS框架中处理数据建模的方式解析
2016/03/05 Javascript
Jquery表单验证失败后不提交的解决方法
2016/10/18 Javascript
Javascript中内建函数reduce的应用详解
2016/10/20 Javascript
JS中parseInt()和map()用法分析
2016/12/16 Javascript
详解JS数组Reduce()方法详解及高级技巧
2017/08/18 Javascript
js设置随机切换背景图片的简单实例
2017/11/12 Javascript
vue cli webpack中使用sass的方法
2018/02/24 Javascript
JS实现骰子3D旋转效果
2019/10/24 Javascript
js实现图片上传到服务器和回显
2020/01/19 Javascript
简单的通用表达式求10乘阶示例
2014/03/03 Python
wxpython学习笔记(推荐查看)
2014/06/09 Python
在python中pandas的series合并方法
2018/11/12 Python
python列表使用实现名字管理系统
2019/01/30 Python
python 字典有序并写入json文件过程解析
2019/09/30 Python
python 实现绘制整齐的表格
2019/11/18 Python
关于Python3 lambda函数的深入浅出
2019/11/27 Python
如何让python的运行速度得到提升
2020/07/08 Python
详解window.open被浏览器拦截的解决方案
2019/07/18 HTML / CSS
欧洲品牌瓷器餐具网上商店:Porzellantreff.de
2018/04/04 全球购物
绘儿乐产品官方在线商店:Crayola.com
2019/09/07 全球购物
EJB的激活机制
2013/10/25 面试题
献爱心倡议书
2014/04/14 职场文书
学校搬迁方案
2014/06/15 职场文书
mybatis中注解与xml配置的对应关系和对比分析
2021/08/04 Java/Android
Redis集群新增、删除节点以及动态增加内存的方法
2021/09/04 Redis
15个值得收藏的JavaScript函数
2021/09/15 Javascript