深浅模式
手写Promise.all实现原理详解
更新: 2024/7/15 字数: 0 字 时长: 0 分钟
🔈 前言:Promise.all是JavaScript中处理并发异步操作的重要工具,能够同时处理多个Promise并等待它们全部完成。本文将深入剖析其实现原理,并手写一个完整的Promise.all实现。
Promise.all的基本概念
Promise.all接收一个Promise数组(或可迭代对象)作为参数,返回一个新的Promise实例。当所有Promise都成功时,返回的Promise状态变为fulfilled,并将所有Promise结果按原顺序存入一个数组;如果有任何一个Promise失败,则立即返回该失败的Promise的结果。
javascript
// Promise.all的基本用法
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = new Promise(resolve => setTimeout(() => resolve(3), 50));
Promise.all([p1, p2, p3])
.then(values => console.log(values)) // [1, 2, 3]
.catch(error => console.error(error));实现原理分析
Promise.all的核心原理包括以下几点:
- 接收可迭代对象:将输入转换为数组处理
- 并发执行:同时处理多个Promise
- 保持顺序:结果数组与输入顺序一致
- 快速失败:任一Promise拒绝则整体拒绝
- 空数组处理:对空数组返回空数组结果
手写实现代码
下面是Promise.all的完整手写实现:
javascript
function myPromiseAll(promises) {
// 判断输入是否可迭代
if (promises == null || typeof promises[Symbol.iterator] !== 'function') {
throw new TypeError(`${typeof promises} ${promises} is not iterable`);
}
// 将可迭代对象转为数组
const promiseArr = Array.from(promises);
return new Promise((resolve, reject) => {
// 空数组直接返回空数组结果
if (promiseArr.length === 0) {
resolve([]);
return;
}
const results = new Array(promiseArr.length); // 存储结果数组
let completedCount = 0; // 已完成的Promise计数
// 处理每个Promise
promiseArr.forEach((promise, index) => {
// 确保每个元素都是Promise
Promise.resolve(promise)
.then(value => {
results[index] = value; // 保持结果顺序
completedCount++; // 完成计数加1
// 所有Promise都已完成,返回结果数组
if (completedCount === promiseArr.length) {
resolve(results);
}
})
.catch(error => {
// 任一Promise失败,整体失败
reject(error);
});
});
});
}实现要点解析
输入验证:
- 检查输入是否为可迭代对象(具有Symbol.iterator方法)
- 使用Array.from将可迭代对象转换为数组
Promise包装:
- 返回一个新的Promise,封装整体结果
- 使用Promise.resolve确保每个元素都是Promise实例
结果收集:
- 创建固定长度的结果数组,保证结果顺序与输入顺序一致
- 使用计数器跟踪已完成的Promise数量
错误处理:
- 任何一个Promise拒绝时,立即拒绝整个Promise.all
- 不再等待其他未完成的Promise
边界情况:
- 处理空数组输入,直接返回空数组结果
测试用例
javascript
// 基本测试
const test1 = myPromiseAll([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3)
]);
test1.then(values => console.log('Test 1:', values)); // [1, 2, 3]
// 异步Promise测试
const test2 = myPromiseAll([
Promise.resolve(1),
new Promise(resolve => setTimeout(() => resolve(2), 100)),
new Promise(resolve => setTimeout(() => resolve(3), 50))
]);
test2.then(values => console.log('Test 2:', values)); // [1, 2, 3]
// 错误处理测试
const test3 = myPromiseAll([
Promise.resolve(1),
Promise.reject('出错了'),
Promise.resolve(3)
]);
test3.then(
values => console.log('Test 3:', values),
error => console.log('Test 3 Error:', error) // '出错了'
);
// 非Promise值测试
const test4 = myPromiseAll([1, 2, 3]);
test4.then(values => console.log('Test 4:', values)); // [1, 2, 3]
// 空数组测试
const test5 = myPromiseAll([]);
test5.then(values => console.log('Test 5:', values)); // []实际应用场景
Promise.all在前端开发中有广泛的应用场景:
- 并行数据请求:
- 同时发起多个API请求并等待所有结果
- 减少页面加载时间,提高用户体验
javascript
// 并行加载用户数据和文章列表
const loadUserAndArticles = async (userId) => {
try {
const [userData, articleList] = await Promise.all([
fetch(`/api/users/${userId}`).then(res => res.json()),
fetch(`/api/articles?author=${userId}`).then(res => res.json())
]);
// 同时获得了用户信息和文章列表
renderUserProfile(userData);
renderArticles(articleList);
} catch (error) {
showError('加载数据失败');
}
};- 资源预加载:
- 同时加载多个图片、脚本或样式文件
- 确保所有资源加载完成后再执行后续操作
javascript
// 预加载多张图片
const preloadImages = (imageUrls) => {
const loadImage = (url) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
img.src = url;
});
};
return Promise.all(imageUrls.map(url => loadImage(url)));
};
// 使用示例
preloadImages(['img1.jpg', 'img2.jpg', 'img3.jpg'])
.then(images => {
console.log('所有图片加载完成');
startSlideshow(images);
})
.catch(error => console.error('图片加载失败', error));- 批量数据处理:
- 并行处理多个数据项
- 等待所有处理完成后进行下一步操作
javascript
// 批量更新多条数据
const batchUpdate = async (items) => {
const updatePromises = items.map(item =>
fetch(`/api/items/${item.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item)
}).then(res => res.json())
);
try {
const results = await Promise.all(updatePromises);
console.log('所有项目更新成功:', results);
return results;
} catch (error) {
console.error('批量更新失败:', error);
throw error;
}
};- 依赖多个异步操作的初始化:
- 应用启动时需要同时完成多个初始化任务
- 所有任务完成后才能进入应用主流程
javascript
// 应用初始化
const initApp = async () => {
try {
await Promise.all([
initDatabase(),
loadUserSettings(),
checkAuthentication(),
loadInitialData()
]);
// 所有初始化任务完成,启动应用
renderApp();
} catch (error) {
showInitError(error);
}
};与其他Promise并发方法的比较
| 方法 | 描述 | 特点 |
|---|---|---|
| Promise.all | 等待所有Promise完成 | 全部成功才成功,一个失败则失败 |
| Promise.allSettled | 等待所有Promise完成 | 返回所有Promise的结果,无论成功失败 |
| Promise.race | 返回最先完成的Promise结果 | 只关注最快的结果,可能成功可能失败 |
| Promise.any | 返回第一个成功的Promise | 只要有一个成功就成功,全部失败才失败 |
总结
Promise.all是处理并发异步操作的强大工具,通过深入理解其实现原理,我们不仅能更好地使用它,还能根据实际需求进行定制和扩展。手写实现Promise.all不仅是一个很好的练习,也帮助我们深入理解JavaScript的异步编程模型和Promise的工作机制。
在实际开发中,合理使用Promise.all可以显著提高应用性能和用户体验,特别是在需要并行处理多个异步操作的场景下。希望本文的详细解析能帮助你更好地掌握这一重要工具。