从零手写简单版Promise:吃透核心原理,为进阶打基础
从 0 开始,手写一个简单版 Promise(后续会更新完整版)。通过手动实现,帮大家搞懂 Promise 最核心的状态管理、resolve/reject 逻辑以及 then 方法的基础实现。文章会逐行拆解代码,尽量做到通俗易懂,适合前端初学者或想巩固 Promise 基础的同学阅读。
一、先明确 Promise 的核心特性(简单版)
在动手写代码之前,我们先梳理一下 Promise 最基础、最核心的特性,这是我们手写的依据:
- 状态不可逆:Promise 有三种状态 —— pending(等待中)、fulfilled(成功)、rejected(失败)。初始状态是 pending,一旦状态变为 fulfilled 或 rejected,就再也不能改变。
- 结果保存:状态变为 fulfilled 时,会保存一个成功结果(resolveValue);状态变为 rejected 时,会保存一个失败原因(rejectValue)。
- resolve/reject 触发状态变更:通过调用 resolve 函数将状态从 pending 改为 fulfilled,调用 reject 函数改为 rejected。
- then 方法接收结果:then 方法接收两个参数 —— onFulfilled(成功回调)和 onRejected(失败回调)。当状态是 fulfilled 时执行 onFulfilled,传入成功结果;状态是 rejected 时执行 onRejected,传入失败原因。
注意:本次实现的是“简单版”,暂不处理异步 executor、then 链式调用、值穿透等高级特性,后续第二版会逐步补充。我们先聚焦核心逻辑,把基础打牢。
二、逐行手写简单版 Promise
我们先给出完整的简单版代码,然后逐部分拆解讲解:
class MyPromise {
constructor(executor) {
// 1. 初始化状态和结果
this.state = "pending";
this.resolveValue = undefined;
this.rejectValue = undefined;
// 2. 定义 resolve 函数
const resolve = (value) => {
// 状态不可逆,只有 pending 时才能修改
if (this.state === "pending") {
this.state = "fulfilled";
this.resolveValue = value;
}
};
// 3. 定义 reject 函数
const reject = (reason) => {
// 状态不可逆,只有 pending 时才能修改
if (this.state === "pending") {
this.state = "rejected";
this.rejectValue = reason;
}
};
// 4. 执行 executor,捕获异常
try {
executor(resolve, reject);
} catch (error) {
// 若 executor 执行报错,直接调用 reject
reject(error);
}
}
// 5. 实现 then 方法
then(onFulfilled, onRejected) {
// 状态为 fulfilled 时,执行成功回调
if (this.state === "fulfilled") {
onFulfilled(this.resolveValue);
}
// 状态为 rejected 时,执行失败回调
if (this.state === "rejected") {
onRejected(this.rejectValue);
}
}
}
2.1 构造函数(constructor)核心逻辑
Promise 的构造函数接收一个 executor 函数作为参数,executor 又接收两个参数 —— resolve 和 reject 函数。构造函数的核心作用是:初始化状态、定义 resolve/reject 方法、执行 executor 并处理异常。
步骤1:初始化状态和结果
this.state = "pending"; // 初始状态:等待中
this.resolveValue = undefined; // 保存成功结果
this.rejectValue = undefined; // 保存失败原因
这三个是 Promise 实例的核心属性:
- state:记录当前 Promise 的状态,初始值为 pending。
- resolveValue:只有当 state 变为 fulfilled 时,才会赋值为成功结果。
- rejectValue:只有当 state 变为 rejected 时,才会赋值为失败原因。
步骤2:定义 resolve 函数
const resolve = (value) => {
// 关键:状态不可逆,只有 pending 时才能修改
if (this.state === "pending") {
this.state = "fulfilled"; // 状态改为成功
this.resolveValue = value; // 保存成功结果
}
};
resolve 函数的作用是“触发成功状态变更”:
- 接收一个参数 value,即成功结果。
- 首先判断当前状态是否为 pending —— 因为状态不可逆,只有 pending 时才能修改为 fulfilled。
- 若状态合法,则修改 state 为 fulfilled,并将 value 保存到 resolveValue 中。
步骤3:定义 reject 函数
const reject = (reason) => {
// 关键:状态不可逆,只有 pending 时才能修改
if (this.state === "pending") {
this.state = "rejected"; // 状态改为失败
this.rejectValue = reason; // 保存失败原因
}
};
reject 函数的作用是“触发失败状态变更”,逻辑和 resolve 类似:
- 接收一个参数 reason,即失败原因(通常是 Error 对象或错误信息字符串)。
- 同样判断状态为 pending 时,才修改为 rejected,并保存失败原因到 rejectValue 中。
步骤4:执行 executor 并捕获异常
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
这一步是构造函数的“核心执行逻辑”:
- 直接调用 executor 函数,并将我们定义的 resolve 和 reject 作为参数传入 —— 这样用户在使用 new Promise 时,就能拿到这两个函数来触发状态变更。
- 用 try-catch 包裹 executor 的执行:如果 executor 执行过程中抛出异常(比如代码写错了),不需要用户手动处理,直接调用 reject 函数,将异常作为失败原因。
2.2 实现 then 方法
then 方法是 Promise 实现“异步结果传递”的核心方法(简单版先实现同步场景),它的核心逻辑是:根据当前 Promise 的状态,执行对应的回调函数,并传入保存的结果/原因。
then(onFulfilled, onRejected) {
// 状态为 fulfilled 时,执行成功回调
if (this.state === "fulfilled") {
onFulfilled(this.resolveValue);
}
// 状态为 rejected 时,执行失败回调
if (this.state === "rejected") {
onRejected(this.rejectValue);
}
}
简单解读:
- then 方法接收两个参数:onFulfilled(成功时执行的回调)和 onRejected(失败时执行的回调)。
- 判断当前 Promise 的状态:如果是 fulfilled,就调用 onFulfilled,并把之前保存的 resolveValue 传进去;如果是 rejected,就调用 onRejected,传入 rejectValue。
- 注意:当前版本的 then 只处理“同步状态变更”(比如 executor 里直接调用 resolve/reject),如果 executor 里有异步操作(比如 setTimeout),then 方法暂时无法正确执行 —— 这部分后期会补充。
三、测试简单版 Promise
测试代码:
let p = new MyPromise((resolve, reject) => {
let isSuccess = true;
if (isSuccess) {
resolve("成功!");
} else {
reject("失败!");
}
}).then(
(result) => {
console.log("成功回调:", result);
},
(error) => {
console.log("失败回调:", error);
}
);
3.1 测试成功场景
当 isSuccess = true 时,executor 里会调用 resolve("成功!"):
- MyPromise 实例初始化,state 为 pending。
- 调用 resolve("成功!"),因为 state 是 pending,所以将 state 改为 fulfilled,resolveValue 设为 "成功!"。
- 调用 then 方法,此时 state 已经是 fulfilled,所以执行 onFulfilled 回调,打印:
成功回调: 成功!。
3.2 测试失败场景
我们把 isSuccess 改为 false,测试失败场景:
let p = new MyPromise((resolve, reject) => {
let isSuccess = false; // 改为失败
if (isSuccess) {
resolve("成功!");
} else {
reject("失败!");
}
}).then(
(result) => {
console.log("成功回调:", result);
},
(error) => {
console.log("失败回调:", error);
}
);
执行结果会打印:失败回调: 失败!,逻辑和成功场景类似,只是状态变为 rejected,执行 onRejected 回调。
3.3 测试 executor 异常场景
我们再测试一下 executor 执行报错的情况:
let p = new MyPromise((resolve, reject) => {
// 故意抛出异常
throw new Error("executor 执行出错了!");
}).then(
(result) => {
console.log("成功回调:", result);
},
(error) => {
console.log("失败回调:", error.message);
}
);
此时,try-catch 会捕获到异常,自动调用 reject,最终打印:失败回调:executor 执行出错了! —— 说明我们的异常处理逻辑生效了。
四、当前版本的局限性
虽然我们的简单版 Promise 能处理同步场景,但它还存在很多局限性,后期会完善一下内容:
- 不支持异步操作:如果 executor 里有异步操作(比如 setTimeout),then 方法会因为状态还是 pending 而无法执行回调。比如:
new MyPromise((resolve) => { `` setTimeout(() => { `` resolve("异步成功"); `` }, 1000); `` }).then(result => { `` console.log(result); // 此时不会执行,因为 then 调用时 state 还是 pending ``}); - 不支持 then 链式调用:现在的 then 方法没有返回新的 Promise,无法实现
promise.then(...).then(...)这样的链式调用。 - 不支持值穿透:当 then 方法没有传入 onFulfilled 或 onRejected 时,应该实现值穿透(比如
promise.then().then(result => console.log(result))应该能拿到结果)。 - 没有处理 onFulfilled/onRejected 不是函数的情况:规范中要求,如果这两个参数不是函数,应该忽略它们(本质也是值穿透的一部分)。
五、总结
今天我们手写了一个简单版的 Promise,核心实现了:
- Promise 的三种核心状态及不可逆特性。
- resolve 和 reject 函数对状态和结果的管理。
- executor 的执行和异常捕获。
- then 方法根据状态执行对应回调的基础逻辑。
通过手动实现,相信大家对 Promise 的核心原理有了更清晰的认识。虽然当前版本还有很多不足,但这是我们理解 Promise 进阶特性的基础。