Skip to main content

JavaScript实现并发请求

· 5 min read
梁昊
Front End Engineer Intern @ Tencent

Promise.all 是 JavaScript 中用于并发处理多个 Promise 的方法,它会返回一个新的 Promise,这个 Promise 只有在传入的所有 Promise 都被解决(fulfilled)时才会成功,并且返回一个包含所有结果的数组。如果其中任何一个 Promise 被拒绝(rejected),则 Promise.all 立即失败,并返回该被拒绝的原因。

接下来,我将解释 Promise.all 的具体实现逻辑,并给出一个简化的手写实现。

1. Promise.all 的核心逻辑

  • 接收一个包含多个 Promise 的可迭代对象(通常是数组)。
  • 并发地执行所有 Promise
  • 等待所有 Promise 都成功后,返回一个包含所有结果的数组。
  • 如果其中一个 Promise 被拒绝,立即返回拒绝的原因,并不会等待其他 Promise 完成。

2. 手写实现 Promise.all

我们可以通过编写一个类似 Promise.all 的函数来理解它的工作原理:

function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
// 如果传入的不是可迭代对象,直接抛出错误
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an iterable'));
}

const results = [];
let completedPromises = 0;

// 如果 promises 是空数组,立即 resolve 空数组
if (promises.length === 0) {
return resolve(results);
}

// 遍历每个 Promise
promises.forEach((promise, index) => {
// 将每个值转换为 Promise,以支持传入非 Promise 的值
Promise.resolve(promise)
.then((value) => {
// 保存结果到对应的 index
results[index] = value;
completedPromises += 1;

// 如果所有 Promise 都完成,resolve 最终结果
if (completedPromises === promises.length) {
resolve(results);
}
})
.catch((error) => {
// 如果任意一个 Promise 被拒绝,立即 reject 整个 Promise.all
reject(error);
});
});
});
}

3. Promise.all 手写实现解析

1. 检查传入参数

  • 首先,检查传入的参数是否是可迭代对象(通常是数组)。如果不是,就直接拒绝(reject)返回。

2. 初始化结果存储和计数器

  • results:用于存储每个 Promise 的解决值。
  • completedPromises:用于计数已完成的 Promise 数量。

3. 处理空数组

  • 如果传入的是一个空数组,立即返回一个解决的 Promise,结果是一个空数组。这是因为没有 Promise 需要等待,所以可以直接返回结果。

4. 遍历每个 Promise

  • 使用 forEach 遍历传入的 Promise 数组。
  • 对于每个 Promise,使用 Promise.resolve 将其转换为一个标准 Promise(这样即使传入的是非 Promise 值,它也会被包装为 Promise)。
  • 然后使用 then 方法处理成功的结果,将结果存储在 results 数组中的对应位置,并增加计数器 completedPromises
  • 一旦所有 Promise 都成功,检查计数器是否等于 promises.length,如果是,则调用 resolve 返回结果数组。

5. 处理失败的情况

  • 使用 catch 捕获每个 Promise 的错误。
  • 一旦有任意一个 Promise 失败(拒绝),立即调用 reject,并返回该错误。Promise.all 会立即停止执行,不会再等待其他 Promise

4. 示例代码

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
setTimeout(resolve, 100, 'foo')
);
const promise3 = Promise.resolve(42);

myPromiseAll([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 'foo', 42]
}).catch((error) => {
console.error('Error:', error);
});

在这个例子中,myPromiseAll 会并发执行 promise1promise2promise3。当它们都成功时,myPromiseAll 返回一个包含所有结果的数组 [3, 'foo', 42]

5. 注意事项

  • 并发处理Promise.all 并不会按顺序执行 Promise,而是并发地启动所有 Promise。这意味着执行顺序取决于每个 Promise 的完成时间,但结果数组的顺序与传入的 Promise 顺序一致。

  • 错误处理:一旦任意一个 Promise 被拒绝,Promise.all 会立即拒绝,并且不会等待其他 Promise 继续执行。

总结

  • Promise.all 是并发执行所有传入的 Promise,并在所有 Promise 都成功时返回包含所有结果的数组。
  • 如果任意一个 Promise 被拒绝,Promise.all 会立即拒绝并返回错误。
  • 手写 Promise.all 的实现可以帮助你更好地理解它的工作机制和异常处理逻辑。