Promise.all
level1: 经典永不过时(❤)
Promise.all =
Promise.all ||
function (promises) {
// 返回一个新的 Promise
return new Promise((resolve, reject) => {
// 处理非数组输入
if (!Array.isArray(promises)) {
return reject(new TypeError("参数必须是一个数组"));
}
// 处理空数组的情况
if (promises.length === 0) {
return resolve([]);
}
const results = []; // 存储所有 Promise 的结果
let completedCount = 0; // 记录已完成的 Promise 数量
const total = promises.length;
// 遍历所有 promise
promises.forEach((promise, index) => {
// 使用 Promise.resolve 包装,确保处理非 Promise 值
Promise.resolve(promise)
.then((value) => {
// 按照原始顺序保存结果
results[index] = value;
completedCount++;
// 当所有 Promise 都完成时,resolve 结果数组
if (completedCount === total) {
resolve(results);
}
})
.catch((error) => {
// 任何一个 Promise reject,立即 reject
reject(error);
});
});
});
};
level2: var + for 循环的经典闭包陷阱(❤)
Promise.all =
Promise.all ||
function (promises) {
return new Promise(function (resolve, reject) {
if (!Array.isArray(promises)) {
return reject(new TypeError("参数必须是一个数组"));
}
if (promises.length === 0) {
return resolve([]);
}
var results = [];
var completedCount = 0;
var total = promises.length;
// 提取处理函数
function handlePromise(promise, index) {
Promise.resolve(promise)
.then(function (value) {
results[index] = value;
completedCount++;
if (completedCount === total) {
resolve(results);
}
})
.catch(function (error) {
reject(error);
});
}
// 循环调用
for (var i = 0; i < promises.length; i++) {
handlePromise(promises[i], i);
}
});
};
闭包 = 内部函数 + 外部变量的引用
如果不使用闭包,那么
-
var是函数作用域,变量提升
-
循环结束后,
i的值是promises.length -
所有
.then()回调共享同一个i变量 -
当异步回调执行时,
i已经是最终值,导致所有结果都写入错误的索引
如果不使用闭包会怎么样?
❌ 问题示例:不提取 handlePromise 函数
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
var results = [];
var completedCount = 0;
var total = promises.length;
for (var i = 0; i < promises.length; i++) {
Promise.resolve(promises[i])
.then(function (value) {
// 问题:i 已经变成了 promises.length!
results[i] = value; // ❌ 错误的索引
completedCount++;
if (completedCount === total) {
resolve(results);
}
});
}
});
};
结果:
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
.then(console.log);
// 期望:[1, 2, 3]
// 实际:[undefined, undefined, undefined, 3] (最后一个值覆盖了所有)
Promise.race
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
for (const promise of promises) {
Promise.resolve(promise).then(resolve, reject);
}
});
};
追问:
Promise.race 与 Promise.all 的区别
// 要求:说明两者的差异和使用场景
Promise.all([p1, p2, p3]); // 全部成功才成功,一个失败就失败
Promise.race([p1, p2, p3]); // 第一个完成(成功/失败)就返回
- 如果
Promise.race([])会怎样?(永远 pending) - 如果
Promise.all([])会怎样?(立即 resolve([]))
推荐不使用 .then() 的第二个参数(容易漏掉回调中的错误)
Promise.allSettled
Promise.allSettled 会等待所有 Promise 完成(无论成功或失败),然后返回一个包含所有结果的数组。
Promise.allSettled = function(promises) {
return Promise.all(
promises.map(p =>
Promise.resolve(p).then(
value => ({ status: 'fulfilled', value }),
reason => ({ status: 'rejected', reason })
)
)
);
};
总结: 把每个 Promise 不管成功失败,都包装成成功的,装进对象里返回,这样就不会中途退出了
// 原本可能失败的 Promise
Promise.reject('错误')
// 包装后变成成功的,只是结果里标记了状态
Promise.resolve({ status: 'rejected', reason: '错误' })
与 Promise.all 的区别
- Promise.all: 任何一个 reject 就立即失败
- Promise.allSettled: 永不 reject,等所有完成后返回结果数组
Promise.any
只要有一个成功就返回,全失败才 reject
Promise.any = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('参数必须是数组'));
}
if (promises.length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
const errors = [];
let rejectedCount = 0;
const total = promises.length;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(resolve)
.catch(error => {
errors[index] = error;
rejectedCount++;
if (rejectedCount === total) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
};