多个promise串行、并行执行

2023/5/24 工具函数

🌙 并行执行

Promise原生方法中提供了执行多个Promise的方式,如:

// 等待所有都成功 或 第一个失败
Promise.all(arr).then(res => console.log(res))

// 等待所有都成功 或 所有都失败
Promise.allSettled(arr).then(res => console.log(res))

// 等待任意一个成功 或 全部失败
Promise.any(arr).then(res => console.log(res))

// 等待任意一个成功 或 任意一个失败
Promise.race(arr).then(res => console.log(res))
1
2
3
4
5
6
7
8
9
10
11

Promise.allPromise.allSettledPromise.anyPromise.race 都是并行执行的,它们并不会按照顺序依次执行每个Promise对象,而是同时执行多个Promise对象。

  • Promise.all 中,所有的Promise对象会同时被执行,而且只有当所有Promise对象都resolve时, Promise.all 返回的Promise对象才会resolve。如果其中任何一个Promisereject了, Promise.all 返回的Promise对象会立即reject

  • Promise.allSettled 中,所有的Promise对象也会同时被执行,不管Promise对象的状态是resolve还是rejectPromise.allSettled 返回的Promise对象都会resolve,返回的是一个包含所有Promise对象状态和结果的对象数组。

  • Promise.any 中,所有的Promise对象同样会同时被执行,但只要有任意一个Promise对象resolvePromise.any 返回的Promise对象就会resolve,并且返回的是该Promise对象的值。

  • Promise.race 中,也是所有的Promise对象同时被执行,但只要有一个Promise对象先resolve或者reject,就会立即返回该Promise对象的状态(resolvereject)和结果。

🌙 串行执行

串行执行是指,不管Promise对象的状态是resolve还是reject,都会依次执行下一个Promise对象。比如:

Task A | ------>|
Task B |         ------>|
Task C |                 ------>|
Task D |                         ------>|
1
2
3
4

准备测试代码:

 // 测试示例
function testFunc(index) {
  return function() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.log('Promise ' + index + ' completed.');
        resolve(index);
      }, Math.random() * 1000);
    });
  };
}
 const arr = [testFunc(1), testFunc(2), testFunc(3)];
1
2
3
4
5
6
7
8
9
10
11
12

🌙 简单版本

Promise.resolve()
  .then(() => {
    return testFunc(1)();
  })
  .then(() => {
    return testFunc(2)();
  })
  .then(() => {
    return testFunc(3)();
  })
1
2
3
4
5
6
7
8
9
10

🌙 使用for循环

function serialPromise(arr) {
  let result = Promise.resolve();
  for (let i = 0; i < arr.length; i++) {
    result = result.then(() => {
      return arr[i]();
    });
  }
  return result;
}
serialPromise(arr).then(() => {
  console.log('All promises completed.');
});
1
2
3
4
5
6
7
8
9
10
11
12

🌙 使用Array.reduce

function serialPromise(arr) {
  return arr.reduce((acc, cur) => {
    return acc.then(() => {
      return cur();
    });
  }, Promise.resolve());
}

serialPromise(arr).then(() => {
  console.log('All promises completed.');
});
1
2
3
4
5
6
7
8
9
10
11

reduce可以实现多个promise串行执行,那么map、forEach、filter等方法可以实现吗?

map 、 forEach 、 filter 等数组方法不适合实现多个Promise串行执行,因为它们都是并行执行函数,它们不会等待前一个函数返回结果后再执行下一个函数。因此,使用这些方法可能会导致Promise对象并行执行,而不是按顺序串行执行它们。 而 reduce 方法可以用来串行执行多个Promise,因为 reduce 方法可以将一个函数的返回结果作为下一个函数的参数,因此,我们可以将每个Promise的返回值作为下一个Promise的参数,使它们按顺序一个接一个地执行。

进一步封装返回执行的结果:

function serialPromise(promiseArray) {
  return promiseArray.reduce((promiseChain, currentPromise) => {
    return promiseChain.then((chainResults) => {
      return currentPromise().then((currentResult) => {
        return [...chainResults, currentResult];
      });
    });
  }, Promise.resolve([]));
}
1
2
3
4
5
6
7
8
9

🌙 使用for wait...of (opens new window)实现

for await...of 语句创建一个循环,该循环遍历异步可迭代对象以及同步可迭代对象

备注: for await...of 不适用于不是异步可迭代的异步迭代器。

async function serialPromise(promiseArr) {
  let result = [];
  for await (const p of promiseArr) {
    result.push(await p());
  }
  return result;
}

serialPromise(arr).then(res => console.log(res)) // [1,2,3]
1
2
3
4
5
6
7
8
9

🌙 优化版

上述代码无法处理错误, 我们可以使用如下方式处理:

export async function serialPromise1(promiseArr: (Promise<any> | (() => Promise<any>))[], callback: (result: any) => void) {
  let resolves = [];

  while(promiseArr.length > 0) {
    let p = promiseArr.shift();
    // 创建新的 Promise, 此时已经把结果放到了 newP 中
    let newP = new Promise((resolve, reject) => {
      if(p instanceof Promise) {
        p.then((res: unknown) => {
          // 成功
          resolve(res)
        }).catch((e: any) => {
          // 失败
          resolve(e)
        })
      }
      if(typeof p === 'function') {
        p().then((res: unknown) => {
          // 成功
          resolve(res)
        }).catch((e: any) => {
          // 失败
          resolve(e)
        })
      }
    })

    resolves.push(newP)
  }
  // 串行执行结束后,将结果放进新的Promise 中,并执行回调
  return Promise.all(resolves).then((arr) => {
    callback(arr)
    return arr
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

测试如下:

import { test, expect } from 'vitest';

test('[serialPromise1] - Sequential Processing 2', async () => {
  const promises = [
    Promise.resolve(1),
    Promise.reject(2),
    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(3);
      }, 500);
    }),
    new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(4);
      }, 200);
    }),

    new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(5);
      }, 600);
    }),
    function() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(6);
        }, 400);
      });
    },
    function() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(7);
        }, 100);
      });
    }
  ];

  // const results: number[] = [];
  const callback = (result:  any[]) => {
    console.log('Result:', result);
    expect(result.map((r: { data: any; }) => r.data)).toEqual([1, 2, 3, 4, 5, 6, 7]);
  };

  await serialPromise1(promises, callback);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

打印结果:

Result: [
  { data: 1, type: 'success' },
  { data: 2, type: 'error' },
  { data: 3, type: 'success' },
  { data: 4, type: 'error' },
  { data: 5, type: 'success' },
  { data: 6, type: 'success' },
  { data: 7, type: 'success' }
]
1
2
3
4
5
6
7
8
9

🌙 reference