如何从零实现一个 Promise
What:Promise 是什么?
如果不看代码,只看概念,Promise 其实很好理解。
-
它是“承诺”的容器:想象你去咖啡店点了一杯拿铁。店员给了你一张小票,这就相当于一个 Promise。
- 这时候咖啡还没做好,小票的状态是 Pending(制作中)。
- 你拿着这个容器(小票),等待未来的结果。
-
它是“一次性开关”(有限状态机):这是 Promise 最核心的概念。“有限状态机”听起来很吓人,其实它就是一个只能按一次的开关。
- Promise 只有三种状态,而且这种变化是不可逆的:
- Pending (进行中):初始状态,开关还没按。
- Fulfilled (已成功):开关拨到了“成功”。(比如咖啡做好了)
- Rejected (已失败):开关拨到了“失败”。(比如牛奶卖完了,做不了)
- Promise 只有三种状态,而且这种变化是不可逆的:
关键点:一旦从 Pending 变成了 Success 或 Failed,就像保险丝熔断了一样,永远定格在那里,不可能再变回去了。这就是“Settled(已决议)”。
简单总结一下就是
从数据结构的角度看,Promise 是一个有限状态机。它只有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。
从功能的角度看,它是一个容器,保存着某个未来才会结束的事件(通常是一个异步操作)的结果。一旦状态从 pending 变为 fulfilled 或 rejected,就称为 "Settled"(已决议),状态将永久锁定,不可逆转。
Why:为什么需要 Promise
在 Promise 出现之前,JavaScript 处理异步主要依靠回调函数(Callback)。这带来了两个巨大不便。
Callback Hell
想象一下,你需要依次完成三个任务:查用户 -> 查订单 -> 查商品。 以前我们只能在回调函数里套回调函数,代码会像金字塔一样向右边无限延伸。
❌ 以前的写法(右侧代码山): 不仅难看,而且极难维护。你想在那堆括弧里找错误,简直是大海捞针。
// 典型的“右侧代码山”
getUser(userId, (err, user) => {
if (err) return handleError(err);
getOrders(user.id, (err, orders) => {
if (err) return handleError(err);
getItem(orders[0].itemId, (err, item) => {
if (err) return handleError(err);
// 终于到达核心逻辑
console.log(item);
});
});
});
✅ Promise 的写法: 它把“嵌套”拉直成了“链条”。代码逻辑像水管一样顺流而下,符合人类的阅读习惯。
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getItem(orders[0].itemId))
.then(item => console.log(item))
.catch(err => handleError(err)); // 这里 catch 可以捕获上面任何一步的错误!
Inversion of Control
这个问题很容易被忽略,但其实更致命。 当你把一个回调函数传给别人的库(比如一个支付 SDK)时,你是把控制权交出去了。 你心里会犯嘀咕:“这个库靠谱吗?万一它出 Bug 了,把我的扣款回调执行了 5 次怎么办?”
❌ 不可靠的第三方:
// 这是一个第三方的统计库
analytics.charge(user, (err, res) => {
// 如果这个库内部写错了循环,你的回调可能会跑 N 次
money.deduct(100);
});
✅Promise 状态机保障 Promise 的状态流转不可逆特性,从机制上保证了 .then 中的代码只会执行一次。
new Promise((resolve) => {
// 即使第三方库发疯了,狂调 resolve...
analytics.charge(user, () => {
resolve(); // 第一次:有效,状态变为 Fulfilled
resolve(); // 第二次:无效!状态已锁定
resolve(); // 第三次:无效!
});
}).then(() => {
// 这里的扣款逻辑,保证只会执行一次
money.deduct(100);
});
How:一步步实现(核心思路)
要实现一个符合 Promise/A+ 规范的 Promise,我们需要构建以下核心模块:
Stage 1:极简骨架(同步状态机)
What & Why
我们要写一个类,它能记录状态(成功还是失败),并能保存结果值。如果连状态都存不住,后面的异步通知、链式调用就无从谈起。而且我们需要实现那个“只能变一次”的状态锁机制。
How
这里使用了 TypeScript,泛型 <T> 你可以简单理解为一个占位符,代表“未来成功时返回的数据类型”。
// 定义三种可能的状态
type State = "pending" | "fulfilled" | "rejected";
class MyPromise<T> {
// 初始化状态,默认为“进行中”
private state: State = "pending";
// 准备一个变量,用来存最终结果(成功的数据 或 失败的原因)
private result: any = undefined;
// 构造函数:当你 new Promise((resolve, reject) => {...}) 时,这个函数会立即执行
constructor(
executor: (
resolve: (value: T) => void,
reject: (reason: any) => void,
) => void,
) {
// 定义 resolve 函数(成功时调用)===
const resolve = (value: T) => {
// 🛑 核心逻辑:状态锁
// 只有当前是 pending 才能变更。如果已经是 fulfilled 或 rejected,直接无视。
if (this.state === "pending") {
this.state = "fulfilled"; // 切换状态
this.result = value; // 保存数据
}
};
// 定义 reject 函数(失败时调用)===
const reject = (reason: any) => {
(. === ) {
. = ;
. = reason;
}
};
{
(resolve, reject);
} (error) {
(error);
}
}
}
p1 = ( {
.();
();
();
});
.(, p1);
1. Start...
2. p1: MyPromise {
state: "fulfilled",
result: "✅ Successful data",
}
Stage 2:支持异步(发布订阅模式)
虽然 Stage 1 的代码看起来很美好,但它有一个致命的缺陷:它只能处理同步任务。
想象一下这个场景:你去咖啡店点单(Promise 初始化),但咖啡需要 5 分钟才能做好(异步)。 目前的 Stage 1 代码是这样执行的:
- 你点单。
- 构造函数执行,遇到
setTimeout(异步),把它挂起。 - 构造函数结束,此时咖啡还没好,状态依然是 Pending。
- 关键问题来了:因为没有
.then方法,我们没办法告诉 Promise:“喂,等会儿咖啡做好了,记得叫我一声(执行回调)。”
如果你在 executor 里写了异步代码,目前的 Promise 就变成了“哑巴”,虽然状态会在未来改变,但没有任何人知道这一刻什么时候发生。
我们需要一种机制,允许我们“预定”未来的结果。
What & Why
我们要实现 .then 方法。 如果 Promise 还没准备好(Pending),我们就把想做的事情(回调函数)先存起来(订阅)。 等到 Promise 准备好了(Resolve/Reject),再把存好的事情拿出来挨个执行(发布)。
这是处理异步编程最经典的设计模式——发布订阅模式 (Publish-Subscribe Pattern)。 就好比你去餐厅排队,服务员给你一个蜂鸣器(订阅)。虽然现在没饭吃(Pending),但等饭好了,蜂鸣器会响(发布),你就可以去取餐了。
How
- 添加两个数组
callbacks,用来保存任务完成后需要执行的逻辑。 - 实现
.then:如果状态是 Pending,就把回调推入数组。 - 修改
resolve/reject:状态改变时,遍历数组并执行回调。
export class MyPromise<T> {
// 新增:订阅者列表(用来存“还没执行”的回调函数)
// 就像一个待办事项清单
private onFulfilledCallbacks: Array<(result: T) => void> = [];
private onRejectedCallbacks: Array<(result: any) => void> = [];
constructor(
executor: (
resolve: (value: T) => void,
reject: (reason: any) => void,
) => void,
) {
const resolve = (result: T) => {
if (this.state === "pending") {
this.state = "fulfilled";
this.result = result;
// 发布:状态一变,赶紧执行清单里所有的回调
this.onFulfilledCallbacks.forEach((callback) => callback(result));
}
};
const reject = (result: any) => {
(. === ) {
. = ;
. = result;
..( (result));
}
};
{
(resolve, reject);
} (error) {
(error);
}
}
() {
(. === && onFulfilled) {
(.);
}
(. === && onRejected) {
(.);
}
(. === ) {
(onFulfilled) ..(onFulfilled);
(onRejected) ..(onRejected);
}
}
}
p1 = ( {
.();
( {
.();
();
}, );
});
p1.( {
.(, data);
});
1. 异步任务开始...
2. 同步代码结束,正在等待...
3. 1秒过去了,准备 resolve
4. 收到通知: 🍔 汉堡做好了
Stage 3:链式调用(Promise 的灵魂)
Stage 2 已经能处理最基本的异步任务了,但它有一个致命缺陷:无法支持链式调用。
试想一下,如果你想连续做三件事:p2.then(吃汉堡).then(喝可乐).then(擦嘴)。
如果你在现在的代码里写 p2.then(...).then(...),你会得到一个报错:Cannot read property 'then' of undefined。
为什么?因为我们现在的 then 方法没有返回值(默认返回 undefined)。
要实现 promise.then().then() 这种丝滑的流水线操作,每一个 .then 必须返回一个新的 Promise 实例,这样下一个 .then 才能接着挂在后面。
What & Why
我们要改造 then 方法。它不再只是简单地执行回调,而是要返回一个新的 MyPromise。我们要把上一个任务的结果,传递给这一个新的 Promise。
如果 then 返回的是 this(同一个 Promise),那状态一旦变成 fulfilled 就改不了了,没法支持后续操作(比如第一个任务成功了,第二个任务失败了)。只有每次都返回一个全新的 Promise,才能让每一个步骤都有自己独立的状态,形成数据处理的“流水线”。
How
- 定义一个
Thenable接口。 - 修改
then方法,使其返回new MyPromise。 - 在新 Promise 内部执行用户的回调。如果用户没传回调(比如
p.then().then(...)),我们需要“穿透”传值。 - 注意:这一阶段我们暂时假设用户在
then里返回的只是普通数值(如数字、字符串),下一阶段处理返回 Promise 的复杂情况。
type State = "pending" | "fulfilled" | "rejected";
export interface Thenable<T> {
then<U>(
onFulfilled?: ((result: T) => U | Thenable<U>) | null,
onRejected?: ((result: any) => U | Thenable<U>) | null,
): Thenable<U>;
}
export class MyPromise<T> {
// 泛型 U 代表下一个 then 返回的新类型
then<U>(
onFulfilled?: ((result: T) => U | Thenable<U>) | null,
onRejected?: ((result: any) => U | Thenable<U>) | null,
): MyPromise<U> {
// 0. 参数默认值(穿透):如果用户没传回调,我们给个默认函数把值往下传
const fulfilledHandler =
onFulfilled ?? ((result: T) => result as unknown as U);
const rejectedHandler =
onRejected ??
((result: any) => {
result;
});
<U>( {
= () => {
{
x = (.);
(x);
} (error) {
(error);
}
};
(. === ) {
(fulfilledHandler);
} (. === ) {
(rejectedHandler);
} {
..( (fulfilledHandler));
..( (rejectedHandler));
}
});
}
}
p3 = <>( {
();
});
p3.( {
.(, val);
val + ;
})
.( {
.(, val);
val * ;
})
.( {
.(, val);
});
1. Initial: 1
2. After adding: 2
3. Final: 20
Stage 4:Promise 解决过程(递归解包)
Stage 3 解决了链式调用,但我们刚才的代码里有一行“偷懒”了:resolve(x)。
如果用户在 .then 里面返回的不是一个普通数字,而是一个新的异步 Promise 呢?
比如:p.then(() => new Promise(...))。
按照 Stage 3 的逻辑,下一个 .then 接收到的 data 会是那个“等待中”的 Promise 对象本身,而不是我们想要的最终结果。
我们需要一种机制,能够自动识别并“解包”返回的 Promise,直到拿到最终的干货为止。
What & Why
这允许异步操作的串联(Task A -> 等待 -> Task B -> 等待 -> Task C)。如果不处理这个,Promise 链中一旦出现异步操作,链条就会断掉(传递下去的是 Promise 对象而不是值)。
How
编写 resolvePromise 函数,逻辑如下:
- 防循环:自己不能等待自己(
p.then(() => p)是非法的)。 - 类型检测:看返回值
x有没有then方法。 - 递归解析:如果
x是 Promise,调用它的then,把结果继续传下去,直到解析出普通值。
// 辅助函数:判断是否为 Thenable
function isThenable<T>(result: any): result is Thenable<T> {
return (
result !== null &&
(typeof result === "object" || typeof result === "function") &&
typeof result.then === "function"
);
}
export class MyPromise<T> {
constructor(
executor: (
resolve: (result: T | MyPromise<T> | Thenable<T>) => void,
reject: (result: any) => void,
) => void,
) {
// resolve 参数类型放宽,允许接收 Promise
const resolve = (result: T | MyPromise<T> | Thenable<T>) => {
if (this.state === "pending") {
// 如果 resolve 接收到一个 Promise,我们需要特殊处理
if (isThenable(result)) {
result.then(resolve as any, reject);
return;
}
this.state = "fulfilled";
this.result = result;
this..( (result T));
}
};
= () => {
(. === ) {
. = ;
. = result;
..( (result));
}
};
{
(resolve, reject);
} (error) {
(error);
}
}
then<U>(
?: ( U | <U> | <U>) | ,
?: ( U | <U> | <U>) | ,
): <U> {
fulfilledHandler =
onFulfilled ?? ( result U);
rejectedHandler =
onRejected ??
( {
result;
});
newPromise = <U>( {
= () => {
( {
{
x = (.);
.(newPromise, x, resolve , reject);
} (error) {
(error);
}
});
};
(. === ) {
(fulfilledHandler);
} (. === ) {
(rejectedHandler);
} {
..( (fulfilledHandler));
..( (rejectedHandler));
}
});
newPromise;
}
resolvePromise<U>(
: <U>,
: ,
: ,
: ,
) {
(newPromise === x) {
( ());
}
called = ;
((x)) {
{
x.(
{
(called) ;
called = ;
.(newPromise, result, resolve, reject);
},
{
(called) ;
called = ;
(result);
},
);
} (error) {
(!called) (error);
}
} {
(x U);
}
}
}
p1 = <>( {
();
});
p1.( {
.(data);
<>( {
( {
();
}, );
});
}).( {
.(, data);
});
Bash
Step 1
(Wait 0.5s...)
Received: Step 2 (Async Result)
Stage 5:静态方法(工具库)
到目前为止,我们的 MyPromise 实例方法(then)已经非常完善,可以处理单个异步任务的链式调用。但在实际开发中,我们经常面临更复杂的场景:
- 并发:同时请求 3 个接口,等它们都完成了再渲染页面?
- 竞速:请求一个接口,如果 3 秒没反应就按超时处理?
- 容错:请求多个备用服务器,只要有一个成功就行?
这时候,我们就需要 Promise 类提供的静态方法 (Static Methods)。
What & Why
我们将实现以下 5 个核心静态方法:
-
MyPromise.resolve(value)- 作用:快速创建一个状态为 Fulfilled 的 Promise。
- 场景:当你有一个现成的值(比如缓存),但 API 要求必须返回 Promise 时。
-
MyPromise.reject(reason)- 作用:快速创建一个状态为 Rejected 的 Promise。
- 场景:在函数开头检测到非法参数,直接返回一个失败的 Promise。
-
MyPromise.all(promises)- 作用:接收一组 Promise,只有全部成功才算成功(返回结果数组);只要有一个失败就立即失败。
- 场景:页面初始化时,需要同时获取“用户信息”和“系统配置”,缺一不可。
-
MyPromise.race(promises)- 作用:接收一组 Promise,谁跑得快就以谁为准(无论成功还是失败)。
- 场景:设置超时控制(请求 Promise vs 定时器 Promise)。
-
MyPromise.allSettled(promises)- 作用:接收一组 Promise,等待全部结束(无论成功失败),返回每个任务的最终状态。
- 场景:批量上传 10 张图片,哪张成功哪张失败都要知道,而不是因为一张失败就全盘崩溃。
-
MyPromise.any(promises)- 作用:接收一组 Promise,只要有一个成功就成功;只有全部失败才算失败。
- 场景:从 3 个 CDN 节点加载同一张图片,哪个先加载出来用哪个。
How
以最复杂的 all 为例:
- 我们需要一个计数器
count。 - 我们需要一个结果数组
results。 - 遍历传入的任务列表,给每个任务绑定
.then()。 - 当某个任务成功时:把结果存入
results对应的下标(保证顺序),count++。如果count等于任务总数,说明全齐了,执行resolve。 - 当某个任务失败时:直接
reject,不用管其他的了。
export class MyPromise<T> {
/** 静态 resolve
* 将一个值转换为 Promise */
static resolve<U>(result: U | MyPromise<U> | Thenable<U>): MyPromise<U> {
// 果参数本身就是 MyPromise 实例,直接返回它(不做任何修改)
if (result instanceof MyPromise) return result;
// 如果是 Thenable (比如 axios 返回的对象),需要包装一层
if (isThenable(result)) {
return new MyPromise((resolve, reject) => {
result.then(resolve, reject);
});
}
// 普通值:返回一个立即成功的 Promise
return new MyPromise((resolve) => resolve(result as U));
}
/** 静态 reject
* 返回一个立即失败的 Promise */
static reject<U = never>(result: any): MyPromise<U> {
return new MyPromise((_, reject) => reject(result));
}
/** 静态 all (全有或全无)
* 并发执行,全部成功才成功,有一个失败就立即失败 */
static all<T>(
promises: Iterable<T | MyPromise<T> | <T>>,
): <T[]> {
( {
arr = .(promises);
: T[] = [];
count = ;
(arr. === ) ([]);
arr.( {
.(p).(
{
results[index] = result;
count++;
(count === arr.) (results);
},
(error),
);
});
});
}
race<T>(
: <T | <T> | <T>>,
): <T> {
( {
( p promises) {
.(p).(resolve, reject);
}
});
}
allSettled<T>(
: <T | <T> | <T>>,
): <[]> {
( {
arr = .(promises);
: [] = [];
count = ;
(arr. === ) ([]);
arr.( {
.(p).(
{
results[index] = { : , result };
(++count === arr.) (results);
},
{
results[index] = { : , : reason };
(++count === arr.) (results);
},
);
});
});
}
<T>(
: <T | <T> | <T>>,
): <T> {
( {
arr = .(promises);
: [] = [];
count = ;
(arr. === )
( ());
arr.( {
.(p).(
(result),
{
errors[index] = reason;
count++;
(count === arr.) (errors);
},
);
});
});
}
}
= () =>
<>( ( (value), ms));
= () =>
<>( ( (reason), ms));
.();
.([
(, ),
(, ),
]).( {
.(, results);
});
.();
.([
(, ),
(, )
]).( {
.(, winner);
});
.();
.([
(, ),
(, ),
(, )
]).( {
.(, val);
});
👉 Testing Promise.all...
👉 Testing Promise.race...
👉 Testing Promise.any...
🏆 Race winner: 🐇 Hare
✅ All finished: [ "A", "B", "C" ]
🌍 Any result: Server 3 works!
Stage 6:完善实例方法 (catch & finally)
这是 Promise 实现的最后一块拼图。我们已经拥有了强大的核心逻辑和静态工具,现在我们要为它加上“语法糖”和生命周期钩子,让它变得真正好用。
What & Why
1. catch
- What: 它其实就是
.then(null, onRejected)的语法糖。 - Why: 为了代码的可读性。写
p.catch(err => ...)显然比写p.then(null, err => ...)更加语义化,专门用来处理错误链。
2. finally
- What:无论 Promise 最终是成功还是失败,都会执行的回调。
- Why: 用于清理工作。比如:发起网络请求时显示
loading转圈圈,无论请求成功还是失败,最后都得把转圈圈关掉。如果没有finally,你得在then和catch里各写一遍hideLoading(),代码重复且丑陋。
How
catch 很好写,直接调用 then 即可。
难点在于 finally。它有两个特殊的规则:
- “透明”传值:
finally不应该改变原有的结果。- 如果之前是成功的,
finally执行完后,应该把成功的value继续往下传。 - 如果之前是失败的,
finally执行完后,应该继续抛出那个reason。
- 如果之前是成功的,
- 等待机制:如果
finally的回调里返回了一个 Promise(比如异步清理数据库连接),Promise 链必须等待这个清理工作完成,才能继续往下走。
export class MyPromise<T> {
// ......
catch<U = T>(
onRejected: (result: any) => U | MyPromise<U> | Thenable<U>,
): MyPromise<U> {
return this.then(null, onRejected);
}
finally(callback: () => void): MyPromise<T> {
const P = this.constructor as typeof MyPromise;
return this.then(
(result) => P.resolve(callback()).then(() => result),
(result) => P.resolve(callback()).then(() => P.reject(result)),
);
}
}
const pFail = new MyPromise((_, reject) => reject("💥 Error!"));
pFail
.catch( {
.(, err);
;
})
.( {
.(, val);
});
.()
.( {
.();
( {
( {
.();
();
}, );
});
})
.( {
.(, finalResult);
});
Caught: 💥 Error!
1. Finally callback running...
After catch: Recovered
2. Async cleanup done (100ms later)
3. Final Result received: ✅ Success Data
完整代码
type State = "pending" | "fulfilled" | "rejected";
export interface Thenable<T> {
then<U>(
onFulfilled?: ((result: T) => U | Thenable<U>) | null,
onRejected?: ((result: any) => U | Thenable<U>) | null,
): Thenable<U>;
}
function isThenable<T>(result: any): result is Thenable<T> {
return (
result !== null &&
(typeof result === "object" || typeof result === "function") &&
typeof result.then === "function"
);
}
export class MyPromise<T> {
private state: State = "pending";
private result: any = undefined;
private onFulfilledCallbacks: Array<(result: T) => void> = [];
private : < > = [];
resolve<U>(: U | <U> | <U>): <U> {
(result ) result;
((result)) {
( {
result.(resolve, reject);
});
}
( (result U));
}
reject<U = >(: ): <U> {
( (result));
}
all<T>(
: <T | <T> | <T>>,
): <T[]> {
( {
arr = .(promises);
: T[] = [];
count = ;
(arr. === ) ([]);
arr.( {
.(p).(
{
results[index] = result;
count++;
(count === arr.) (results);
},
(error),
);
});
});
}
race<T>(
: <T | <T> | <T>>,
): <T> {
( {
( p promises) {
.(p).(resolve, reject);
}
});
}
allSettled<T>(
: <T | <T> | <T>>,
): <[]> {
( {
arr = .(promises);
: [] = [];
count = ;
(arr. === ) ([]);
arr.( {
.(p).(
{
results[index] = { : , result };
(++count === arr.) (results);
},
{
results[index] = { : , : reason };
(++count === arr.) (results);
},
);
});
});
}
<T>(
: <T | <T> | <T>>,
): <T> {
( {
arr = .(promises);
: [] = [];
count = ;
(arr. === )
( ());
arr.( {
.(p).(
(result),
{
errors[index] = reason;
count++;
(count === arr.) (errors);
},
);
});
});
}
() {
= () => {
(. === ) {
((result)) {
result.(resolve , reject);
;
}
. = ;
. = result;
..( (result T));
}
};
= () => {
(. === ) {
. = ;
. = result;
..( (result));
}
};
{
(resolve, reject);
} (error) {
(error);
}
}
then<U>(
?: ( U | <U> | <U>) | ,
?: ( U | <U> | <U>) | ,
): <U> {
fulfilledHandler =
onFulfilled ?? ( result U);
rejectedHandler =
onRejected ??
( {
result;
});
newPromise = <U>( {
= () => {
( {
{
x = (.);
.(newPromise, x, resolve , reject);
} (error) {
(error);
}
});
};
(. === ) {
(fulfilledHandler);
} (. === ) {
(rejectedHandler);
} {
..( (fulfilledHandler));
..( (rejectedHandler));
}
});
newPromise;
}
<U = T>(
: U | <U> | <U>,
): <U> {
.(, onRejected);
}
(: ): <T> {
P = . ;
.(
P.(()).( result),
P.(()).( P.(result)),
);
}
resolvePromise<U>(
: <U>,
: ,
: ,
: ,
) {
(newPromise === x) {
( ());
}
called = ;
((x)) {
{
x.(
{
(called) ;
called = ;
.(newPromise, result, resolve, reject);
},
{
(called) ;
called = ;
(result);
},
);
} (error) {
(!called) (error);
}
} {
(x U);
}
}
}