从「似懂非懂」到「了如指掌」:Promise 与原型链全维度拆解
前言
在前端世界里,Promise 和 原型链(Prototype) 是两个看似毫不相干,却又互相影响、甚至能相互解释的重要概念。
很多人学习 Promise 时,会关注它的使用:then、catch、finally、Promise.all 等;
学习原型链时,又会关注 __proto__、prototype、构造函数与实例之间的关系。
但鲜有人把 Promise 本身也是一个对象,它也依赖原型链运作 这件事真正联系起来讲透。
本文将以一次完整的 Promise 异步流程为主线,把“原型链 + 状态机 + 微任务”融合讲解,让你完全理解 Promise 到底是怎么在底层“跑”起来的。
一、Promise 为什么是“对象”?
我们常常写:
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve('OK'), 1000)
})
很多人知道 Promise 是“异步解决方案”,但忽略了一个基本事实:
Promise 是一个构造函数(类),你创建的是它的实例。
也就是说:
-
Promise—— 构造函数(带prototype) -
p—— 实例对象(带__proto__)
打开控制台试试:
console.log(p.__proto__ === Promise.prototype) // true
这里马上就把原型链扯进来了。
🔍 Promise.prototype 上都有啥?
输入:
console.log(Promise.prototype)
你会看到:
then: ƒ then()
catch: ƒ catch()
finally: ƒ finally()
constructor: ƒ Promise()
...
这说明:
所有 Promise 实例都是通过原型链访问 then/catch/finally 的。
也就是说 p.then() 并不是实例自身有,而是:
p ---> Promise.prototype ---> Object.prototype ---> null
这为后文理解 Promise “链式调用”机制奠定基础。
二、原型链视角下,看懂 Promise 的执行流
我们直接看一个你提供的代码精简版:
const p = new Promise((resolve, reject) => {
console.log(111)
setTimeout(() => {
reject('失败1')
}, 1000)
})
console.log(222)
p.then(data => {
console.log(data)
}).catch(err => {
console.log(err)
}).finally(() => {
console.log('finally')
})
输出顺序:
111
222
失败1
finally
要理解为什么 Promise 能这样执行,必须从两个角度讲:
- (1)Promise 内部是状态机(pending → fulfilled / rejected)
- (2)then/catch/finally 是通过原型链挂载的“回调注册器”
我们分开看看。
1)Promise 内部是一个状态机
内部状态(无法手动修改):
| 状态 | 描述 | 何时出现 |
|---|---|---|
| pending | 初始状态 | 执行 executor 期间 |
| fulfilled | resolve 被调用 | 成功 |
| rejected | reject 被调用 | 失败 |
也就是说:
new Promise(executor)
执行后:
- 立即执行
executor - executor 只在同步阶段运行
- 真正的 resolve/reject 回调是“挂起来”,等事件循环驱动
所以你看到:
111(executor 同步执行)
222(外部同步执行)
失败1(异步到点后 reject)
finally(状态 settled 后触发)
2)then/catch/finally:它们不是魔法,是原型链的方法
看看这段链式调用:
p.then(...).catch(...).finally(...)
为什么可以一直“链式”?
因为每次调用 then 都 返回一个新的 Promise 实例:
p.then(...) → p2
p2.catch(...) → p3
p3.finally(...) → p4
这几个实例的原型链依然是:
p2.__proto__ === Promise.prototype
p3.__proto__ === Promise.prototype
...
因此:
链式本质 = 每次链式都返回一个新的 Promise 实例,然后继续在原型链上查找 then/catch/finally。
这就是原型链在 Promise 底层的重要性。
三、原型链的类比:Promise 就像“火车头 + 车厢”系统
你提到的类比非常棒,我把它整理成完整模型:
✨ Promise = 火车系统
- 构造函数(Promise) = 火车制造厂
- 原型对象(Promise.prototype) = “火车车厢模板”
- 实例(p) = 火车头
- then/catch/finally = 可以接在车头后的“车厢类型”
于是我们看到:
p(车头).then(挂一个车厢)
.then(再挂一节)
.catch(挂一个处理失败的车厢)
.finally(挂尾部的清理车厢)
每次挂车厢(调用 then/catch)时,都会生成 新的火车车头(新的 Promise 实例) 。
整个火车最终沿着轨道(事件循环)开动到终点。
⚠️ 注意:为什么 finally 一定执行?
因为 finally 不关心结果,只关心火车是否开到终点(settled)。
四、Promise 与普通对象原型链的对比
你提供了一个经典例子:
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.speci = '人类'
let zhen = new Person('白兰地空瓶', 18)
console.log(zhen.speci)
const kong = {
name: '空瓶',
hobbies: ['读书', '喝酒']
}
zhen.__proto__ = kong
console.log(zhen.hobbies, zhen.speci)
输出:
人类
['读书','喝酒'] undefined
这个例子非常适合用来对比 Promise 的原型链逻辑。
对比 1:实例可以动态改原型(不推荐)
zhen.__proto__ = kong 改掉了原来的 Person.prototype
所以:
- 能访问
hobbies:因为来自kong - 不能访问
speci:因为已脱离 Person.prototype
Promise 则不能做这种事
你不能这样做:
p.__proto__ = {}
否则:
p.then is not a function
因为 then/catch/finally 都来自 Promise.prototype。
这反而让我们更清楚地理解:
Promise 的能力几乎全部来自原型链。
五、Promise.all 的底层逻辑:一辆多车头的“联挂火车”
提到 Promise.all,这里正好顺便讲讲它的底层设计。
Promise.all([p1, p2, p3])
机制可以用一个形象类比解释:
- 假设有三辆火车(p1/p2/p3)
- Promise.all 创建一辆“总火车头” pAll
- pAll 盯着三个火车头,只要全部变成 fulfilled,就把所有结果一次性返回
- 如果有一个 reject,则整个 pAll 变成 rejected(列车脱轨)
也就是说:
Promise.all = 多个 Promise 状态机的并联 + 一个新的总状态机。
为什么它能做到?
答案依旧在原型链:
- Promise.all 本质是一个静态方法,返回新的 Promise 实例
- 新的 Promise 实例依然沿用同一套路(prototype → then/catch)
六、用真实工程场景收尾:Promise 原型链为什么重要?
在真实项目里,理解 Promise 的原型机制有三个实际价值:
① debugger 时能看清原型链,定位异步回调来源
你能区分:
- then 回调从哪里来的?(Promise.prototype.then)
- promise 链断在哪一层?
② 手写 Promise 时必须实现 then/catch/finally
如果你手写 Promise A+:
MyPromise.prototype.then = function(onFulfilled, onRejected) {}
这里你就必须自己处理链式、状态机、回调队列。
③ 能理解 async/await 的底层依赖 Promise 链式调度
await 会把后续步骤注册到 promise.then 中。
理解 then 的原型链,就能理解 async/await 的机制本质。
七、总结:Promise + 原型链的全景图
// 创建实例
const p = new Promise(executor)
// 原型链:调用能力来自这里
p.__proto__ = Promise.prototype
// 状态机:内部维护 pending → fulfilled/rejected
// then/catch/finally:注册微任务
// 链式调用:每次都返回一个新的 Promise 实例
// Promise.all:多个状态机的并联
一句话总结:
Promise 本质是一个基于“原型链 + 状态机 + 微任务队列”的异步调度框架。
它既是面向对象设计(通过原型链复用方法),又是异步控制核心工具(内部状态机)。
理解二者的融合,你就真正吃透了 Promise。