Node.js 中常见的 Async/Await 使用场景解析

  • 时间:2020-04-24 19:56 作者:1024译站 来源: 阅读:725
  • 扫一扫,手机访问
摘要:async/await真是个好东西。有了它,在 Node.js 中写异步代码简直如丝般顺滑,再也不需要引入第三方库来帮忙了,还能摆脱复杂的 Promise 链式调用。async/await的应用场景有哪些?最佳实践又是什么?本文为你逐个道来。重试失败请求在 Node 中发送 HTTP 请求,假如请求

async/await真是个好东西。有了它,在 Node.js 中写异步代码简直如丝般顺滑,再也不需要引入第三方库来帮忙了,还能摆脱复杂的 Promise 链式调用。async/await的应用场景有哪些?最佳实践又是什么?本文为你逐个道来。

重试失败请求

在 Node 中发送 HTTP 请求,假如请求失败就重试指定的次数。用回调函数的方式大概会这么写:

const superagent = require('superagent');const NUM_RETRIES = 3;request('http://google.com/this-throws-an-error', function(error, res) {  console.log(error.message); // "Not Found"});function request(url, callback) {  _request(url, 0, callback);}function _request(url, retriedCount, callback) {  superagent.get(url).end(function(error, res) {    if (error) {      if (retriedCount >= NUM_RETRIES) {        return callback && callback(error);      }      return _request(url, retriedCount + 1, callback);    }    callback(res);  });}

也不算太难,但是用到了递归,对初学者来说略微有点绕。另外,这里还存在一个小问题:假如superagent.get().end()这个函数调用本身抛出了同步异常时怎样办?我们可能需要在 _request() 外面再包一层try/catch用于捕获所有异常。所有调用这个方法的地方都需要这么做,这就有点麻烦了,还容易出错。比照下用async/await 实现同样效果的代码,只要要for循环 和 try/catch

const superagent = require('superagent');const NUM_RETRIES = 3;test();async function test() {  let i;  for (i = 0; i < NUM_RETRIES; ++i) {    try {      await superagent.get('http://google.com/this-throws-an-error');      break;    } catch(err) {}  }  console.log(i); // 3}

这段代码的巧妙之处在于,请求无异常就会退出for循环,否则进入下一次循环,直到超过指定次数。没有回调,没有递归,看上去是不是清晰多了?但是要注意, await 必需在async 函数内部使用,嵌套包含也不行。下面代码中 forEach()的回调函数不是async函数,因而不能使用await

const superagent = require('superagent');const NUM_RETRIES = 3;test();async function test() {  let arr = new Array(NUM_RETRIES).map(() => null);  arr.forEach(() => {    try {      // SyntaxError: Unexpected identifier. This `await` is not in an async function!      await superagent.get('http://google.com/this-throws-an-error');    } catch(err) {}  });}

解决 MongoDB 游标

MongoDB 的 find() 返回一个 游标。游标实际上是一个带有异步 next() 函数的对象,next用于获取查询结果中的下一个文档。假如所有结果都获取完毕,next()就返回null。MongoDB 游标有几个助手函数,比方 each()map()toArray(),mongoose ODM 还添加了一个额外的eachAsync(),不过这些都只是next()上面的语法糖。

没有async/await的话,手动调用next() 重试失败请求也要用到递归,跟前面的例子一样。有了async/await,你会发现再也不用上那些助手函数了(除了toArray()),由于只需用for循环遍历游标就行了,简单直接!

const mongodb = require('mongodb');test();async function test() {  const db = await mongodb.MongoClient.connect('mongodb://localhost:27017/test');  await db.collection('Movies').drop();  await db.collection('Movies').insertMany([    { name: 'Enter the Dragon' },    { name: 'Ip Man' },    { name: 'Kickboxer' }  ]);  // 获取游标对象  const cursor = db.collection('Movies').find();  // 用 `next()` 和 `await` 移动游标  for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {    console.log(doc.name);  }}

假如觉得还不够方便,还可以用async迭代器(ES2018 引入的特性,Node.js 10.x 开始支持)。

const cursor = db.collection('Movies').find().map(value => ({  value,  done: !value}));for await (const doc of cursor) {  console.log(doc.name);}

并发执行异步任务

上面的例子都是按顺序执行多个请求,任何时候都只有一个next()函数在执行。如何并发执行多个异步任务?假设你要用 bcrypt同时执行多个密码哈希操作:

const bcrypt = require('bcrypt');const NUM_SALT_ROUNDS = 8;test();async function test() {  const pws = ['password', 'password1', 'passw0rd'];  // `promises` 是 promise 数组, `bcrypt.hash()` 假如没指定回调函数,就会返回 promise  const promises = pws.map(pw => bcrypt.hash(pw, NUM_SALT_ROUNDS));  /**   * Prints hashed passwords, for example:   * [ '$2a$08$nUmCaLsQ9rUaGHIiQgFpAOkE2QPrn1Pyx02s4s8HC2zlh7E.o9wxC',   *   '$2a$08$wdktZmCtsGrorU1mFWvJIOx3A0fbT7yJktRsRfNXa9HLGHOZ8GRjS',   *   '$2a$08$VCdMy8NSwC8r9ip8eKI1QuBd9wSxPnZoZBw8b1QskK77tL2gxrUk.' ]   */  console.log(await Promise.all(promises));}

Promise.all() 函数接受一个 promise 数组作为参数,并返回一个 promise。传入的多个 promise 一律被处理后,返回的 promise 才被处理,同时接收到一个数组,包含每个 promise 的执行结果。因为每个 bcrypt.hash()返回一个 promise,promises就是个 promise 数组,await Promise.all(promises) 的结果就是每个bcrypt.hash()调用的结果。

Promise.all()不是并行解决多个异步函数的唯一方式,还有 Promise.race() 函数 ,也可以并发执行多个 promise,区别是它在最先完成的 promise 之后被处理。race的字面意思就是“竞赛”,看哪个 promise 先完成。下面是Promise.race()配合使用async/await的例子:

/** * Prints below: * waited 250 * resolved to 250 * waited 500 * waited 1000 */test();async function test() {  const promises = [250, 500, 1000].map(ms => wait(ms));  console.log('resolved to', await Promise.race(promises));}async function wait(ms) {  await new Promise(resolve => setTimeout(() => resolve(), ms));  console.log('waited', ms);  return ms;}

需要注意的是,尽管 Promise.race()在第一个被完成的 promise 之后就被处理,但是其他的async 函数仍然会继续执行。由于 promise 目前是无法取消执行的。

总结

Async/await 被认为是 JavaScript 异步编程的终极方案。这个特性不但减少了代码量,还极大地提高了可读性。在异常解决、重试请求和执行并发任务方面有很大的优势,值得大力推广使用。


看到这个颇有气质的 logo,不来关注下吗?

  • 全部评论(0)
最新发布的资讯信息
【系统环境|】2FA验证器 验证码如何登录(2024-04-01 20:18)
【系统环境|】怎么做才能建设好外贸网站?(2023-12-20 10:05)
【系统环境|数据库】 潮玩宇宙游戏道具收集方法(2023-12-12 16:13)
【系统环境|】遥遥领先!青否数字人直播系统5.0发布,支持真人接管实时驱动!(2023-10-12 17:31)
【系统环境|服务器应用】克隆自己的数字人形象需要几步?(2023-09-20 17:13)
【系统环境|】Tiktok登录教程(2023-02-13 14:17)
【系统环境|】ZORRO佐罗软件安装教程及一键新机使用方法详细简介(2023-02-10 21:56)
【系统环境|】阿里云 centos 云盘扩容命令(2023-01-10 16:35)
【系统环境|】补单系统搭建补单源码搭建(2022-05-18 11:35)
【系统环境|服务器应用】高端显卡再度登上热搜,竟然是因为“断崖式”的降价(2022-04-12 19:47)
手机二维码手机访问领取大礼包
返回顶部