Promise中.all(), .race(), .allSettled()菜鸟教程_Promise菜鸟知识

从ES6 开始,我们大都使用的是 Promise.all()和Promise.race(),Promise.allSettled() 提案已经到第4阶段,因此将会成为ECMAScript 2020的一部分。1.概述Promise.all(promises: Iterable<Promise>): Promise>Promise.

Promise中.all(), .race(), .allSettled()菜鸟教程

从ES6 开始,我们大都使用的是 Promise.all()和Promise.race(),Promise.allSettled() 提案已经到第4阶段,因此将会成为ECMAScript 2020的一部分。

Promise中.all(), .race(), .allSettled()菜鸟教程_Promise菜鸟知识

1.概述

Promise.all<T>(promises: Iterable<Promise<T>>): Promise<Array<T>>

  • Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果

Promise.race<T>(promises: Iterable<Promise<T>>): Promise<T>

  • Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

Promise.allSettled<T>(promises: Iterable<Promise<T>>): Promise<Array<SettlementObject<T>>>

  • Promise.allSettled()方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。

回顾: Promise 状态

给定一个返回Promise的异步操作,以下这些是Promise的可能状态:

  • pending: 初始状态,既不是成功,也不是失败状态。
  • fulfilled: 意味着操作成功完成。
  • rejected: 意味着操作失败。
  • Settled: Promise要么被完成,要么被拒绝。Promise一旦达成,它的状态就不再改变。

Promise中.all(), .race(), .allSettled()菜鸟教程_Promise菜鸟知识

3.什么是组合

又称部分-整体模式,将对象整合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性,它基于两种函数:

  • 基元函数(简短:基元)创建原子块。
  • 组合函数(简称:组合)将原子和/或复合件组合在一起以形成复合件。

对于 JS 的 Promises 来说

  • 基元函数包括:Promise.resolve()、Promise.reject()
  • 组合函数:Promise.all(), Promise.race(), Promise.allSettled()

4. Promise.all()

Promise.all()的类型签名:

  • Promise.all<T>(promises: Iterable<Promise<T>>): Promise<Array<T>>

返回情况:

完成(Fulfillment):
如果传入的可迭代对象为空,Promise.all 会同步地返回一个已完成(resolved)状态的promise。
如果所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promise,Promise.all 返回的 promise 异步地变为完成。
在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。

失败/拒绝(Rejection):
如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成。

来个例子:

const promises = [
  Promise.resolve('a'),
  Promise.resolve('b'),
  Promise.resolve('c'),
];
Promise.all(promises)
  .then((arr) => assert.deepEqual(
    arr, ['a', 'b', 'c']
  ));

如果其中的一个 promise 被拒绝,那么又是什么情况:

const promises = [
  Promise.resolve('a'),
  Promise.resolve('b'),
  Promise.reject('ERROR'),
];
Promise.all(promises)
  .catch((err) => assert.equal(
    err, 'ERROR'
  ));

下图说明Promise.all()是如何工作的

Promise中.all(), .race(), .allSettled()菜鸟教程_Promise菜鸟知识

4.1 异步 .map() 与 Promise.all()

数组转换方法,如.map()、.filter()等,用于同步计算。例如

function timesTwoSync(x) {
  return 2 * x;
}
const arr = [1, 2, 3];
const result = arr.map(timesTwoSync);
assert.deepEqual(result, [2, 4, 6]);

如果.map()的回调是基于Promise的函数会发生什么? 使用这种方式 .map()返回的的结果是一个Promises数组。

Promises数组不是普通代码可以使用的数据,但我们可以通过Promise.all()来解决这个问题:它将Promises数组转换为Promise,并使用一组普通值数组来实现。

function timesTwoAsync(x) {
  return new Promise(resolve => resolve(x * 2));
}
const arr = [1, 2, 3];
const promiseArr = arr.map(timesTwoAsync);
Promise.all(promiseArr)
  .then(result => {
    assert.deepEqual(result, [2, 4, 6]);
  });

更实际工作上关于 .map()示例

接下来,咱们使用.map()和Promise.all()从Web下载文件。 首先,咱们需要以下帮助函数:

function downloadText(url) {
  return fetch(url)
    .then((response) => { // (A)
      if (!response.ok) { // (B)
        throw new Error(response.statusText);
      }
      return response.text(); // (C)
    });
}

downloadText()使用基于Promise的fetch API 以字符串流的方式下载文件:

  • 首先,它异步检索响应(第A行)。
  • response.ok(B行)检查是否存在“找不到文件”等错误。
  • 如果没有错误,使用.text()(第C行)以字符串的形式取回文件的内容。

在下面的示例中,咱们 下载了两个文件

const urls = [
  'http://example.com/first.txt',
  'http://example.com/second.txt',
];

const promises = urls.map(
  url => downloadText(url));

Promise.all(promises)
  .then(
    (arr) => assert.deepEqual(
      arr, ['First!', 'Second!']
    ));

Promise.all()的一个简版实现

function all(iterable) {
  return new Promise((resolve, reject) => {
    let index = 0;
    for (const promise of iterable) {
      // Capture the current value of `index`
      const currentIndex = index;
      promise.then(
        (value) => {
          if (anErrorOccurred) return;
          result[currentIndex] = value;
          elementCount++;
          if (elementCount === result.length) {
            resolve(result);
          }
        },
        (err) => {
          if (anErrorOccurred) return;
          anErrorOccurred = true;
          reject(err);
        });
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    let anErrorOccurred = false;
    const result = new Array(index);
  });
}

5. Promise.race()

Promise.race()方法的定义:

Promise.race<T>(promises: Iterable<Promise<T>>): Promise<T>

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。来几个例子,瞧瞧:

const promises = [
  new Promise((resolve, reject) =>
    setTimeout(() => resolve('result'), 100)), // (A)
  new Promise((resolve, reject) =>
    setTimeout(() => reject('ERROR'), 200)), // (B)
];
Promise.race(promises)
  .then((result) => assert.equal( // (C)
    result, 'result'));

在第 A 行,Promise 是完成状态 ,所以 第 C 行会执行(尽管第 B 行被拒绝)。

如果 Promise 被拒绝首先执行,在来看看情况是嘛样的:

const promises = [
  new Promise((resolve, reject) =>
    setTimeout(() => resolve('result'), 200)),
  new Promise((resolve, reject) =>
    setTimeout(() => reject('ERROR'), 100)),
];
Promise.race(promises)
  .then(
    (result) => assert.fail(),
    (err) => assert.equal(
      err, 'ERROR'));

注意,由于 Promse 先被拒绝,所以 Promise.race() 返回的是一个被拒绝的 Promise

这意味着Promise.race([])的结果永远不会完成。

下图演示了Promise.race()的工作原理:

Promise中.all(), .race(), .allSettled()菜鸟教程_Promise菜鸟知识

Promise.race() 在 Promise 超时下的情况

在本节中,我们将使用Promise.race()来处理超时的 Promise。 以下辅助函数:

function resolveAfter(ms, value=undefined) {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(value), ms);
  });
}

resolveAfter() 主要做的是在指定的时间内,返回一个状态为 resolve 的 Promise,值为为传入的 value

调用上面方法:

function timeout(timeoutInMs, promise) {
  return Promise.race([
    promise,
    resolveAfter(timeoutInMs,
      Promise.reject(new Error('Operation timed out'))),
  ]);
}

timeout() 返回一个Promise,该 Promise 的状态取决于传入 promise 状态 。

其中 timeout 函数中的 resolveAfter(timeoutInMs, Promise.reject(new Error(‘Operation timed out’)) ,通过 resolveAfter 定义可知,该结果返回的是一个被拒绝状态的 Promise。

再来看看timeout(timeoutInMs, promise)的运行情况。如果传入promise在指定的时间之前状态为完成时,timeout 返回结果就是一个完成状态的 Promise,可以通过.then的第一个回调参数处理返回的结果。

timeout(200, resolveAfter(100, 'Result!'))
  .then(result => assert.equal(result, 'Result!'));

相反,如果是在指定的时间之后完成,刚 timeout 返回结果就是一个拒绝状态的 Promise,从而触发catch方法指定的回调函数。

timeout(100, resolveAfter(2000, 'Result!'))
  .catch(err => assert.deepEqual(err, new Error('Operation timed out')));

重要的是要了解“Promise 超时”的真正含义:

  1. 如果传入入Promise 较到的得到解决,其结果就会给返回的 Promise。
  2. 如果没有足够快得到解决,输出的 Promise 的状态为拒绝。

也就是说,超时只会阻止传入的Promise,影响输出 Promise(因为Promise只能解决一次), 但它并没有阻止传入Promise的异步操作。

5.2 Promise.race() 的一个简版实现

以下是 Promise.race()的一个简化实现(它不执行安全检查)

function race(iterable) {
  return new Promise((resolve, reject) => {
    for (const promise of iterable) {
      promise.then(
        (value) => {
          if (settlementOccurred) return;
          settlementOccurred = true;
          resolve(value);
        },
        (err) => {
          if (settlementOccurred) return;
          settlementOccurred = true;
          reject(err);
        });
    }
    let settlementOccurred = false;
  });
}

6.Promise.allSettled()

“Promise.allSettled”这一特性是由Jason Williams,Robert Pamely和Mathias Bynens提出。

promise.allsettle()方法的定义:

  • Promise.allSettled<T>(promises: Iterable<Promise<T>>)
    : Promise<Array<SettlementObject<T>>>

它返回一个Array的Promise,其元素具有以下类型特征:

type SettlementObject<T> = FulfillmentObject<T> | RejectionObject;

interface FulfillmentObject<T> {
  status: 'fulfilled';
  value: T;
}

interface RejectionObject {
  status: 'rejected';
  reason: unknown;
}

Promise.allSettled()方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。

举例说明, 比如各位用户在页面上面同时填了3个独立的表单, 这三个表单分三个接口提交到后端, 三个接口独立, 没有顺序依赖, 这个时候我们需要等到请求全部完成后给与用户提示表单提交的情况

在多个promise同时进行时咱们很快会想到使用Promise.all来进行包装, 但是由于Promise.all的短路特性, 三个提交中若前面任意一个提交失败, 则后面的表单也不会进行提交了, 这就与咱们需求不符合.

Promise.allSettled跟Promise.all类似, 其参数接受一个Promise的数组, 返回一个新的Promise, 唯一的不同在于, 其不会进行短路, 也就是说当Promise全部处理完成后我们可以拿到每个Promise的状态, 而不管其是否处理成功.

下图说明promise.allsettle()是如何工作的

Promise中.all(), .race(), .allSettled()菜鸟教程_Promise菜鸟知识

6.1 Promise.allSettled() 例子

这是Promise.allSettled() 使用方式快速演示示例

Promise.allSettled([
  Promise.resolve('a'),
  Promise.reject('b'),
])
.then(arr => assert.deepEqual(arr, [
  { status: 'fulfilled', value:  'a' },
  { status: 'rejected',  reason: 'b' },
]));

6.2 Promise.allSettled() 较复杂点的例子

这个示例类似于.map()和Promise.all()示例(我们从其中借用了downloadText()函数):我们下载多个文本文件,这些文件的url存储在一个数组中。但是,这一次,咱们不希望在出现错误时停止,而是希望继续执行。Promise.allSettled()允许咱们这样做:

const urls = [
  'http://example.com/exists.txt',
  'http://example.com/missing.txt',
];

const result = Promise.allSettled(
  urls.map(u => downloadText(u)));
result.then(
  arr => assert.deepEqual(
    arr,
    [
      {
        status: 'fulfilled',
        value: 'Hello!',
      },
      {
        status: 'rejected',
        reason: new Error('Not Found'),
      },
    ]
));

6.3 Promise.allSettled() 的简化实现

这是promise.allsettle()的简化实现(不执行安全检查)

function allSettled(iterable) {
  return new Promise((resolve, reject) => {
    function addElementToResult(i, elem) {
      result[i] = elem;
      elementCount++;
      if (elementCount === result.length) {
        resolve(result);
      }
    }

    let index = 0;
    for (const promise of iterable) {
      // Capture the current value of `index`
      const currentIndex = index;
      promise.then(
        (value) => addElementToResult(
          currentIndex, {
            status: 'fulfilled',
            value
          }),
        (reason) => addElementToResult(
          currentIndex, {
            status: 'rejected',
            reason
          }));
      index++;
    }
    if (index === 0) {
      resolve([]);
      return;
    }
    let elementCount = 0;
    const result = new Array(index);
  });
}

7. 短路特性

Promise.all() 和 romise.race() 都具有 短路特性

  • Promise.all(): 如果参数中 promise 有一个失败(rejected),此实例回调失败(reject)

Promise.race():如果参数中某个promise解决或拒绝,返回的 promise就会解决或拒绝。

8.并发性和 Promise.all()

8.1 顺序执行与并发执行

考虑下面的代码:

asyncFunc1()
  .then(result1 => {
    assert.equal(result1, 'one');
    return asyncFunc2();
  })
  .then(result2 => {
    assert.equal(result2, 'two');
  });

使用.then()顺序执行基于Promise的函数:只有在 asyncFunc1()的结果被解决后才会执行asyncFunc2() 。

而 Promise.all() 是并发执行的

Promise.all([asyncFunc1(), asyncFunc2()])
  .then(arr => {
    assert.deepEqual(arr, ['one', 'two']);
  });

9.2 并发技巧:关注操作何时开始

确定并发异步代码的技巧:关注异步操作何时启动,而不是如何处理它们的Promises。

例如,下面的每个函数都同时执行asyncFunc1()和asyncFunc2(),因为它们几乎同时启动。

function concurrentAll() {
  return Promise.all([asyncFunc1(), asyncFunc2()]);
}

function concurrentThen() {
  const p1 = asyncFunc1();
  const p2 = asyncFunc2();
  return p1.then(r1 => p2.then(r2 => [r1, r2]));
}

另一方面,以下两个函数依次执行asyncFunc1()和asyncFunc2(): asyncFunc2()仅在asyncFunc1()的解决之后才调用。

function sequentialThen() {
  return asyncFunc1()
    .then(r1 => asyncFunc2()
      .then(r2 => [r1, r2]));
}

function sequentialAll() {
  const p1 = asyncFunc1();
  const p2 = p1.then(() => asyncFunc2());
  return Promise.all([p1, p2]);
}

9.3 Promise.all() 与 Fork-Join 分治编程

Promise.all() 与并发模式“fork join”松散相关。重温一下咱们前面的一个例子:

Promise.all([
    // (A) fork
    downloadText('http://example.com/first.txt'),
    downloadText('http://example.com/second.txt'),
  ])
  // (B) join
  .then(
    (arr) => assert.deepEqual(
      arr, ['First!', 'Second!']
    ));
  • Fork:在A行中,分割两个异步任务并同时执行它们。
  • Join:在B行中,对每个小任务得到的结果进行汇总。
海计划公众号
(0)
上一篇 2020/03/29 01:41
下一篇 2020/03/29 01:41

您可能感兴趣的内容

  • javascript如何循环遍历对象?入门基础知识_遍历菜鸟教程下载

    在JavaScript中有多种循环遍历对象的方法,下面本篇文章就来给大家介绍一下使用JavaScript循环遍历对象的方法,希望对大家有所帮助。1、使用for 循环for 循环是 Js 中最常用的一个循环工具,经常用于数组的循环遍历。let arr = [1,2,3];
    for (let i=0; i<arr.length; i++){console.log

    2020/03/22
  • 实现短链接服务(Node 使用攻略 Express MongoDB)教程视频_链接

    短链接我们或多或少都使用过,所谓短链接就是根据较长的原链接url生成一段较短的链接,访问短链接可以跳转到对应的原链接,这样做好处在于:1. url更加美观;2. 便于保存和传播;3. 某些网站内容发布有字数限制,短链接可以节约字数。短链接实现的原理非常简单,可以概括为:为每个原链接生成不重复的唯一短链接将原链接和对应短链接成对保存到数据库访问短链接时,web

    2020/03/29
  • 实现一个JS深拷贝函数基础知识入门_函数入门基础教程

    JS深拷贝概念并不新鲜,但是真正要真正理解原理还是有点难度的。这也是JS语言精粹之一吧。例子let a = {name: ‘demo’,age: 18
    };let b = a;
    b.name = ‘demo1’;console.log(a); // 输出 {name: “demo1”, age: 18}
    console.log(b); // 输出 {name

    2020/03/29
  • MyScript小白入门_公式字符化网站

    MyScript小白入门 官方网址:http://webdemo.myscript.com/ 简介描述:公式字符化网站 有些复杂的公式什么的是不是总是用电脑打不出来?试试这个神器,…

    2020/03/10
  • 代码审查问题手册基础知识_代码新手入门

    代码审查列表,是代码审查的明确规则和指导手册,它可以使代码审查为你的团队带来更多好处,并且能够显著提升代码审查的速度。研究表明,使用代码审查列表的审阅者的表现要优于不使用的审阅者。所以不管你是新手开发者还是经验丰富的开发者,开始考虑使用代码审查列表吧。代码作者应该关注的列表作为代码的作者,你应该保证:代码编译成功并且通过静态检查(没有警告)代码通过所有的测试

    2020/03/24
  • 处理Linux文件的3个技巧入门百科_Linux入门基础

    Linux 提供了许多用于查找、计数和重命名文件的命令。这有一些有用的选择。Linux 提供了多种用于处理文件的命令,这些命令可以节省你的时间,并使你的工作不那么繁琐。查找文件当你查找文件时, find 可能会是第一个想到的命令,但是有时精心设计的 ls 命令会更好。想知道你昨天离开办公室回家前调用的脚本么?简单!使用 ls 命令并加上 -ltr 选项。最后

    2020/03/29
  • 英选小白教程_以定制开发外包服务为主的外包项目平台

    英选小白教程 官方网址:https://www.yingxuan.io/ 简介描述:以定制开发外包服务为主的外包项目平台 英选在高质量与性价比间找到一个完美平衡点,英选不仅能解决业…

    2020/03/11
  • rewire小白攻略_NPM测试模块、Node.js单元测试

    rewire小白攻略 GitHub:https://github.com/jhnns/rewire 简介描述:NPM测试模块、Node.js单元测试 rewire为模块添加了一个特…

    2020/03/06
  • 探索小程序实现菜鸟教程下载_小程序教程视频

    随着小程序的发展与功能的逐步完善,越来越多的产品需要小程序与 APP 的功能能有一些共性,社区跨平台的解决方案越来越多,比如 taro 等为代表的把一套代码编译成多端运行的机制,本文会使用 Swift 作为原生语言,在 iOS 应用上运行一个小程序 Demo, 使用 Android && React Native 也可以采用同样的思路实现。相关代码仓库: h

    2020/03/20
  • 1024节日的由来,程序员的你最想对自己说的是什么?【1024程序员节日】使用帮助_程序员使用攻略

    “吃饭睡觉写代码”基本都程序猿的大部分生活了,作为从事最累的职业之一的我们终于有了自己的节日,那就是10月24日。最开始知道这个数字的时候,记得好像是来自某某某论坛。其实1024是2的十次方,二进制计数的基本计量单位之一。程序员(英文Programmer)是从事程序开发、维护的专业人员。1G=1024M,1M=1024Kb,1kb=1024bit,程序员们就

    2020/04/06
  • clearfix小白基础清除浮动float的多种clearfix方法总汇_float菜鸟指南

    浮动float是常用的css属性,可以设置左浮动float:left;右浮动float:right;不浮动float:none;浮动会影响到前后标签 、引起父容器塌陷,导致页面布局出错等问题。这篇文章就整理清除float的多种方法:1.使用overflow属性.clearfix{overflow:hidden;width:100%;
    }在浮动元素的父元素添加

    2020/04/06
  • ArangoDB攻略教程_一个原生的多模型数据库

    ArangoDB攻略教程 官方网址:https://www.arangodb.com GitHub:https://github.com/arangodb/arangodb 简介描…

    2020/03/11
  • HTML5使用指南CSS3响应式垂直时间轴,高端,大气入门攻略_响应式

    HTML5+CSS3响应式垂直时间轴,使用了HTML5标签

    ,时间轴中所有的内容包括标题、简介、时间和图像都放在.cd-timeline-block的DIV中,多个DIV形成一个序列,并把这些DIV放在

    中。PC端:移动端:html代码:
    <meta charset="ut

    2020/04/03
  • JS 高阶函数入门基础教程_函数入门基础知识

    Function Object什么是函数?在大多数编程语言中,函数是一段独立的代码块,用来抽象处理某些通用功能的方法;主要操作是给函数传入特定对象(参数),并在方法调用结束后获得一个新的对象(返回值)。function greeting(name) {return `Hello ${name}`;
    }console.log( greeting(‘Onion’

    2020/03/20
  • Js如何禁止复制粘贴?小白知识_拷贝基础教程

    JavaScript中可以使用oncopy事件来禁止复制,oncopy事件在用户拷贝元素上的内容时触发。使用onpaste事件禁止粘贴,onpaste事件在用户向元素中粘贴文本时触发。oncopy事件:定义和用法oncopy 事件在用户拷贝元素上的内容时触发。提示: oncopy 事件在用户拷贝元素时也会触发,例如, 拷贝 元素。提示: onco

    2020/03/20
  • CardTabs小白教程一款简单的jquery Tabs选项卡插件

    CardTabs基础入门 官方网址:https://cardtabs.js.org GitHub:https://github.com/blekerfeld/CardTabs 简介…

    2020/03/05