Node.js中常见的异步/等待设计模式

Node.js中常见的异步/等待设计模式

Node.js中的异步/等待打开了一系列强大的设计模式。现在可以使用基本语句和循环来完成过去采用复杂库或复杂承诺链接的任务。我已经用co编写了这些设计模式,但异步/等待使得这些模式可以在vanilla Node.js中访问,不需要外部库。iffor

Node.js中常见的异步/等待设计模式

重试失败的请求

其强大之await处在于它可以让你使用同步语言结构编写异步代码。例如,下面介绍如何使用回调函数使用superagent HTTP库重试失败的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中以处理所有异常。必须在任何地方这样做都很麻烦并且容易出错。随着异步/ AWAIT,你可以写只用同等功能fortry/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
}

相信我,这是有效的。我记得我第一次尝试这种模式与合作,我感到莫名其妙,它实际工作。但是,下面的就不能正常工作。请记住,await必须始终在async函数中,而传递给forEach()下面的闭包不是async

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()解析为空。MongoDB游标有几个辅助函数,如each(),,map()toArray(),猫鼬ODM增加了一个额外的eachAsync()函数,但它们都只是语法上的糖next()

没有异步/等待,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' }
  ]);

  // Don't `await`, instead get a cursor
  const cursor = db.collection('Movies').find();
  // Use `next()` and `await` to exhaust the cursor
  for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
    console.log(doc.name);
  }
}

如果这对你来说不够方便,有一个TC39的异步迭代器建议可以让你做这样的事情。请注意,下面的代码并没有在Node.js的任何目前发布的版本工作,这只是什么是可能在未来的一个例子。

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` is an array of promises, because `bcrypt.hash()` returns a
  // promise if no callback is supplied.
  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()函数接受一组承诺,并返回一个承诺,等待数组中的每个承诺解析,然后解析为一个数组,该数组包含解析的原始数组中每个承诺的值。每个bcrypt.hash()调用都会返回一个promise,所以promises在上面的数组中包含一组promise,并且value的值await Promise.all(promises)是每个bcrypt.hash()调用的结果。

Promise.all()并不是您可以并行处理多个异步函数的唯一方式,还有一个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()在第一个承诺解决后解决,但其余的async功能仍然继续执行。请记住,承诺不可取消。

继续

异步/等待是JavaScript的巨大胜利。使用这两个简单的关键字,您可以从代码库中删除大量外部依赖项和数百行代码。您可以添加强大的错误处理,重试和并行处理,只需一些简单的内置语言结构。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/101598.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • JAVA设计模式之抽象工厂模式

    本文属于23种设计模式系列。继续前面简单工厂模式和工厂方法模式的例子,以汽车配件制造介绍抽象工厂模式。

  • 北风设计模式课程—9、原型模式的作用和意义[通俗易懂]

    北风设计模式课程—9、原型模式的作用和意义[通俗易懂]北风设计模式课程—9、原型模式的作用和意义

  • PHP八大设计模式「建议收藏」

    PHP八大设计模式「建议收藏」PHP命名空间可以更好地组织代码,与Java中的包类似。Test1.php

  • C++ 23种设计模式(6)-适配器模式

    C++ 23种设计模式(6)-适配器模式适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。它包括类适配器和对象适配器,本文针对的是对象适配器。举个例子,在STL中就用到了适配器模式。STL实现了一种数据结构,称为双端队列(deque),支持前后两段的插入与删除。STL实现栈和队列时,没有从头开始定义它们,而是直接使用双端队列实现的。这里双端队列就扮演了适配器的角色。队列用到了它的后端插入,前端删除。而栈用到了它的后端插入,后端删除。假设栈和队列都是一种顺序容器,有两种操作:压入和弹出。

  • JAVA设计模式——适配器模式

    JAVA设计模式——适配器模式适配器模式是一种结构型设计模式。适配器模式的思想是:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。用电器来打个比喻:有一个电器的插头是三脚的,而现有的插座是两孔的,要使插头插上插座,我们需要一个插头转换器,这个转换器即是适配器。适配器模式涉及3个角色:源(Adaptee):需要被适配的对象或类型,相当于插头。适配器(Ad

  • 设计模式之桥接(bridge)模式

    在现实生活中,我们常常会用到两种或多种类型的笔,比如毛笔和蜡笔。假设我们需要大、中、小三种类型的画笔来绘制12中不同的颜色,如果我们使用蜡笔,需要准备3*12=36支。但如果使用毛笔的话,只需要提供3

    2021年12月19日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号