🧠 深入理解 JavaScript Promise 与 `Promise.all`:从原型链到异步编程实战
在现代 JavaScript 开发中,Promise 是处理异步操作的核心机制之一。ES6 引入的 Promise 极大地简化了“回调地狱”(Callback Hell)问题,并为后续的 async/await 语法奠定了基础。而 Promise.all 则是并发执行多个异步任务并统一处理结果的强大工具。
本文将结合 原型链原理、Promise 基础用法 和 实际示例代码,带你系统掌握 Promise 及其静态方法 Promise.all 的使用与底层逻辑。
🔗 一、JavaScript 的面向对象:原型链而非“血缘”
在深入 Promise 之前,我们先厘清一个关键概念:JavaScript 的继承不是基于“类”的血缘关系,而是基于原型(prototype)的链式查找机制。
1.1 🏗️ 构造函数与原型对象
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.speci = '人类';
let zhen = new Person('张三', 18);
console.log(zhen.speci); // 输出: "人类"
-
Person是构造函数。 -
Person.prototype是所有Person实例共享的原型对象。 -
zhen.__proto__指向Person.prototype。 -
Person.prototype.constructor又指回Person,形成闭环。
🚂 小比喻:可以把
constructor看作“车头”,prototype是“车身”。实例通过__proto__连接到车身,而车身知道自己的车头是谁。
1.2 ⚡ 动态修改原型链(不推荐)
const kong = {
name: '孔子',
hobbies: ['读书', '喝酒']
};
zhen.__proto__ = kong;
console.log(zhen.hobbies); // ✅ 输出: ['读书', '喝酒']
console.log(kong.prototype); // ❌ undefined!普通对象没有 prototype 属性
⚠️ 注意:
- 只有函数才有
prototype属性;- 普通对象(如
kong)只有__proto__,没有prototype。- 在这里kong是object的一个实例
kong.__prpto__ == object.prototype💡 虽然可以动态修改
__proto__,但会破坏代码可预测性,影响性能,应避免使用。
⏳ 二、Promise:ES6 的异步解决方案
2.1 🧩 Promise 基本结构
<script>
const p = new Promise((resolve, reject) => {
console.log(111); // 同步执行
setTimeout(() => {
console.log(333);
// resolve('结果1'); // 成功
reject('失败1'); // 失败
}, 1000);
});
console.log(222);
console.log(p, '////////'); // 此时 p 状态仍是 pending
console.log(p.__proto__ == Promise.prototype); // true
</script>
📋 执行顺序分析:
-
111立即输出(executor 函数同步执行)✅ -
222紧接着输出 ✅ -
p此时处于 pending(等待) 状态 ⏳ - 1 秒后,
333输出,调用reject('失败1'),状态变为 rejected ❌ -
.catch()捕获错误,.finally()无论成功失败都会执行 🔁
2.2 🎯 Promise 的三种状态
- ⏳ pending:初始状态,既不是成功也不是失败。
-
✅ fulfilled:操作成功完成(通过
resolve触发)。 -
❌ rejected:操作失败(通过
reject触发)。
🔒 核心特性:一旦状态改变,就不可逆。这是 Promise 的设计基石。
2.3 🔍 原型关系验证
console.log(p.__proto__ === Promise.prototype); // ✅ true
-
p是Promise的实例。 - 所有 Promise 实例的
__proto__都指向Promise.prototype。 -
Promise.prototype上定义了.then(),.catch(),.finally()等方法。 Promise.prototype.__proto__ == object.prototype
🚀 三、Promise.all:并发处理多个异步任务
3.1 ❓ 什么是 Promise.all?
Promise.all(iterable) 接收一个可迭代对象(如数组),其中包含多个 Promise。它返回一个新的 Promise:
- ✅ 全部成功 → 返回一个包含所有结果的数组(顺序与输入一致)。
- ❌ 任一失败 → 立即 rejected,返回第一个失败的原因。
3.2 💻 使用示例
const task1 = fetch('/api/user'); // 假设返回 { id: 1, name: 'Alice' }
const task2 = fetch('/api/posts'); // 假设返回 [{ title: 'JS' }]
const task3 = new Promise(resolve => setTimeout(() => resolve('done'), 500));
Promise.all([task1, task2, task3])
.then(([user, posts, msg]) => {
console.log('全部完成:', user, posts, msg);
})
.catch(err => {
console.error('某个任务失败:', err);
});
🌐 适用场景:需要同时加载用户信息、文章列表、配置数据等,全部就绪后再渲染页面。
3.3 ⚠️ 错误处理演示
const p1 = Promise.resolve('成功1');
const p2 = Promise.reject('失败2');
const p3 = Promise.resolve('成功3');
Promise.all([p1, p2, p3])
.then(results => console.log('不会执行'))
.catch(err => console.log('捕获错误:', err)); // 输出: "失败2"
❗ 关键点:只要有一个失败,整个
Promise.all就失败,其余成功的 Promise 结果会被丢弃。
3.4 🛡️ 替代方案:Promise.allSettled(ES2020)
如果你希望无论成功失败都等待所有任务完成,可以使用 Promise.allSettled:
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach((res, i) => {
if (res.status === 'fulfilled') {
console.log(`✅ 任务${i} 成功:`, res.value);
} else {
console.log(`❌ 任务${i} 失败:`, res.reason);
}
});
});
✅ 适用于:批量上传、日志收集、非关键资源加载等场景。
📚 四、总结:从原型到实践
| 概念 | 说明 |
|---|---|
| 🔗 原型链 | JS 对象通过 __proto__ 查找属性,constructor 指回构造函数 |
| ⏳ Promise | 表示异步操作的最终完成或失败,具有 pending/fulfilled/rejected 三种状态 |
| 🧩 Promise.prototype | 所有 Promise 实例的方法来源(.then, .catch 等) |
| 🚀 Promise.all | 并发执行多个 Promise,全成功则成功,任一失败则整体失败 |
| 🛡️ 最佳实践 | 使用 Promise.all 提升性能;用 allSettled 处理非关键任务 |
💭 五、思考题
- 🤔 为什么
console.log(p)在setTimeout之前打印时,状态是pending? - 🛠️ 能否通过修改
Promise.prototype.then来全局拦截所有 Promise 的成功回调?这样做有什么风险? - 📦 如果
Promise.all中传入空数组[],结果会是什么?
💡 答案提示:
- 因为异步任务尚未执行,状态未改变。
- 技术上可行,但会破坏封装性、可测试性和团队协作,强烈不推荐。
- 立即 resolved,返回空数组
[]—— 这是符合规范的!
通过本文,你不仅掌握了 Promise 和 Promise.all 的用法,还理解了其背后的 原型机制 和 异步执行模型。这将为你编写健壮、高效的异步代码打下坚实基础。🌟
Happy Coding! 💻✨