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


Posted in Javascript onNovember 20, 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)
Javascript 相关文章推荐
JavaScript Archive Network 集合
May 12 Javascript
实例讲解js验证表单项是否为空的方法
Jan 09 Javascript
jQuery实现滚动鼠标放大缩小图片的方法(附demo源码下载)
Mar 05 Javascript
Javascript中call,apply,bind方法的详解与总结
Dec 12 Javascript
Vue中引入样式文件的方法
Aug 18 Javascript
javaScript 连接打印机,打印小票的实例
Dec 29 Javascript
Taro集成Redux快速上手的方法示例
Jun 21 Javascript
mongodb初始化并使用node.js实现mongodb操作封装方法
Apr 02 Javascript
javascript实现拖拽碰撞检测
Mar 12 Javascript
JS正则表达式常见函数与用法小结
Apr 13 Javascript
javascript设计模式 ? 备忘录模式原理与用法实例分析
Apr 21 Javascript
移动端JS实现拖拽两种方法解析
Oct 12 Javascript
利用three.js画一个3D立体的正方体示例代码
Nov 19 #Javascript
IE11下使用canvas.toDataURL报SecurityError错误的解决方法
Nov 19 #Javascript
使用DataTable插件实现异步加载数据
Nov 19 #Javascript
原生JavaScript实现Ajax异步请求
Nov 19 #Javascript
gulp安装以及打包合并的方法教程
Nov 19 #Javascript
js实现rem自动匹配计算font-size的示例
Nov 18 #Javascript
如何编写一个完整的Angular4 FormText 组件
Nov 18 #Javascript
You might like
CI框架中libraries,helpers,hooks文件夹详细说明
2014/06/10 PHP
php可应用于面包屑导航的迭代寻找家谱树实现方法
2015/02/02 PHP
Symfony2函数用法实例分析
2016/03/18 PHP
Centos PHP 扩展Xchche的安装教程
2016/07/09 PHP
php 替换文章中的图片路径,下载图片到本地服务器的方法
2018/02/06 PHP
简单谈谈node.js 版本控制 nvm和 n
2015/10/15 Javascript
基于JavaScript实现Json数据根据某个字段进行排序
2015/11/24 Javascript
JQuery移动页面开发之屏幕方向改变与滚屏的实现
2015/12/03 Javascript
JavaScript实现瀑布流布局
2020/06/28 Javascript
Vue.js 60分钟快速入门教程
2017/03/28 Javascript
javascript数组去重常用方法实例分析
2017/04/11 Javascript
Vue数组更新及过滤排序功能
2017/08/10 Javascript
在ES5与ES6环境下处理函数默认参数的实现方法
2018/05/13 Javascript
解决vue-cli单页面手机应用input点击手机端虚拟键盘弹出盖住input问题
2018/08/25 Javascript
使用Object.defineProperty如何巧妙找到修改某个变量的准确代码位置
2018/11/02 Javascript
[01:18:45]DOTA2-DPC中国联赛 正赛 DLG vs Dragon BO3 第三场2月1日
2021/03/11 DOTA
Python实现的排列组合计算操作示例
2017/10/13 Python
PyTorch线性回归和逻辑回归实战示例
2018/05/22 Python
Django+Ajax+jQuery实现网页动态更新的实例
2018/05/28 Python
Python使用装饰器模拟用户登陆验证功能示例
2018/08/24 Python
flask框架jinja2模板与模板继承实例分析
2019/08/01 Python
python实现从wind导入数据
2019/12/03 Python
Python faker生成器生成虚拟数据代码实例
2020/07/20 Python
详解matplotlib绘图样式(style)初探
2021/02/03 Python
python FTP编程基础入门
2021/02/27 Python
找到不普通的东西:Bonanza
2016/10/20 全球购物
化工专业大学生职业生涯规划书
2014/01/14 职场文书
小学作文评语大全
2014/04/21 职场文书
政治表现评语
2014/05/04 职场文书
党员国庆节演讲稿范文2014
2014/09/21 职场文书
泰山导游词
2015/02/02 职场文书
施工员岗位职责范本
2015/04/11 职场文书
大学开学典礼新闻稿
2015/07/17 职场文书
珍爱生命主题班会
2015/08/13 职场文书
python中super()函数的理解与基本使用
2021/08/30 Python
利用Sharding-Jdbc进行分库分表的操作代码
2022/01/22 Java/Android