首先,我们先明确 Promise 的核心特性,再分步实现:
- Promise 有三种状态:
pending(等待)、fulfilled(成功)、rejected(失败) - 状态只能从
pending转为fulfilled或pending转为rejected,一旦改变就无法再修改(不可逆) - 每个 Promise 实例都有
then方法,用于注册成功/失败回调 - 回调函数是异步执行的(微任务,这里用
setTimeout模拟,简化实现) - 支持
then方法的链式调用(核心是then方法返回一个新的 Promise)
分步实现手写 Promise
第一步:实现基础结构(状态、构造函数)
先搭建 Promise 的基本骨架,处理状态初始化和执行器函数(executor)。
// 定义三种状态常量,避免魔法字符串
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
// 构造函数,接收执行器函数 executor(立即执行)
constructor(executor) {
try {
// 执行器接收两个参数:resolve(成功回调)、reject(失败回调)
executor(this.resolve, this.reject);
} catch (error) {
// 执行器内部抛出异常,直接触发 reject
this.reject(error);
}
}
// 初始化状态为 pending
status = PENDING;
// 存储成功结果
value = undefined;
// 存储失败原因
reason = undefined;
// 成功回调函数(使用箭头函数,绑定 this 指向,避免调用时 this 丢失)
resolve = (value) => {
// 只有 pending 状态才能修改,保证状态不可逆
if (this.status !== PENDING) return;
// 更改状态为成功
this.status = FULFILLED;
// 存储成功结果
this.value = value;
};
// 失败回调函数(同理,箭头函数绑定 this)
reject = (reason) => {
if (this.status !== PENDING) return;
// 更改状态为失败
this.status = REJECTED;
// 存储失败原因
this.reason = reason;
};
}
第二步:实现 then 方法(基础版,目前只能支持同步操作)
then 方法接收两个参数:onFulfilled(成功回调)、onRejected(失败回调),并根据当前 Promise 状态执行对应回调。
下面代码执行的顺序是executor->resolve/reject->onFulfilled/onRejected
class MyPromise {
// 省略第一步中的构造函数、resolve、reject(同上,直接复用)
status = PENDING;
value = undefined;
reason = undefined;
constructor(executor) {
try {
executor(this.resolve, this.reject);
} catch (error) {
this.reject(error);
}
}
resolve = (value) => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
};
reject = (reason) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
};
// 实现 then 方法
then(onFulfilled, onRejected) {
// 处理回调参数默认值(如果用户没传,透传结果/原因)
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e; };
// 1. 状态为 fulfilled:直接执行成功回调
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
// 2. 状态为 rejected:直接执行失败回调
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
第三步:处理异步场景(回调队列)
上面的实现只支持同步执行器(比如 new MyPromise((res) => res(1))),如果执行器是异步的(比如 new MyPromise((res) => setTimeout(() => res(1), 1000))),then 调用时状态还是 pending,此时需要把回调存入队列,等待状态改变后再执行。
下面代码的执行顺序是:executor的同步代码->.then的回调onFulfilled, onRejected被推入回调队列->executor的异步代码执行完毕->resolve/reject->执行回调队列中的回调函数
class MyPromise {
status = PENDING;
value = undefined;
reason = undefined;
// 定义两个队列,存储异步场景下的成功/失败回调
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
constructor(executor) {
try {
executor(this.resolve, this.reject);
} catch (error) {
this.reject(error);
}
}
resolve = (value) => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
// 状态改变后,执行队列中所有成功回调
this.onFulfilledCallbacks.forEach(callback => callback());
};
reject = (reason) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
// 状态改变后,执行队列中所有失败回调
this.onRejectedCallbacks.forEach(callback => callback());
};
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e; };
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
// 3. 状态为 pending:将回调存入对应队列,等待后续执行
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
}
}
}
第四步:实现 then 方法链式调用(核心难点)
链式调用的关键是:then 方法必须返回一个新的 Promise 实例,并且将上一个 then 的回调返回值,作为新 Promise 的成功结果;如果回调抛出异常,新 Promise 则触发失败。
同时,要保证回调的异步执行(符合 Promise/A+ 规范,这里用 setTimeout 模拟微任务)。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
status = PENDING;
value = undefined;
reason = undefined;
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
constructor(executor) {
try {
executor(this.resolve, this.reject);
} catch (error) {
this.reject(error);
}
}
resolve = (value) => {
if (this.status !== PENDING) return;
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback());
};
reject = (reason) => {
if (this.status !== PENDING) return;
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback());
};
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) => v;
onRejected = typeof onRejected === 'function' ? onRejected : (e) => { throw e; };
// 链式调用核心:返回一个新的 MyPromise 实例
const newPromise = new MyPromise((resolve, reject) => {
// 封装成功回调的执行逻辑(复用代码)
const handleFulfilled = () => {
// 用 setTimeout 模拟异步执行,保证符合 Promise/A+ 规范
setTimeout(() => {
try {
// 执行上一个 then 的成功回调,获取返回值
const result = onFulfilled(this.value);
// 处理返回值:如果是 Promise,等待其状态改变;否则直接 resolve
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
// 回调执行抛出异常,新 Promise 直接 reject
reject(error);
}
}, 0);
};
// 封装失败回调的执行逻辑(复用代码)
const handleRejected = () => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolvePromise(newPromise, result, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
};
// 处理三种状态
if (this.status === FULFILLED) {
handleFulfilled();
}
if (this.status === REJECTED) {
handleRejected();
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
// 返回新的 Promise 实例,支持链式调用
return newPromise;
}
}
// 辅助函数:处理 then 回调的返回值,兼容返回值为 Promise 的场景
function resolvePromise(newPromise, result, resolve, reject) {
// 避免循环引用(比如 then 回调返回 newPromise 本身)
if (newPromise === result) {
return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));
}
// 情况 1:返回值是 MyPromise 实例(或标准 Promise)
if (result instanceof MyPromise) {
// 等待该 Promise 状态改变,透传其结果/原因
result.then(resolve, reject);
} else {
// 情况 2:返回值是普通值(undefined、对象、函数等),直接 resolve
resolve(result);
}
}
第五步:补充常用辅助方法(可选,增强功能)
为了更贴近原生 Promise,我们可以补充 catch、finally 方法:
class MyPromise {
// 省略上面已实现的构造函数、resolve、reject、then 方法(同上)
// catch 方法:本质是 then 方法的语法糖(只传失败回调,不传成功回调)
catch(onRejected) {
return this.then(undefined, onRejected);
}
// finally 方法:无论成功还是失败,都会执行回调,且透传结果/原因
finally(onFinally) {
return this.then(
(value) => {
// 执行 finally 回调,等待回调执行完成(如果是 Promise 则等待)
return MyPromise.resolve(onFinally()).then(() => value);
},
(reason) => {
return MyPromise.resolve(onFinally()).then(() => { throw reason; });
}
);
}
// 静态 resolve 方法:快速返回一个成功状态的 MyPromise
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise((resolve) => resolve(value));
}
// 静态 reject 方法:快速返回一个失败状态的 MyPromise
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}
}
测试手写 Promise
我们用几个场景测试一下实现效果,验证是否符合预期:
// 测试 1:同步执行器
const p1 = new MyPromise((resolve) => {
resolve(100);
}).then(res => {
console.log(res); // 100
return res * 2;
}).then(res => {
console.log(res); // 200
});
// 测试 2:异步执行器
const p2 = new MyPromise((resolve) => {
setTimeout(() => {
resolve(200);
}, 1000);
}).then(res => {
console.log(res); // 1 秒后输出 200
return new MyPromise((resolve) => resolve(res * 3));
}).then(res => {
console.log(res); // 1 秒后输出 600
}).catch(err => {
console.log(err);
});
// 测试 3:异常处理
const p3 = new MyPromise((resolve, reject) => {
reject('出错了');
}).catch(err => {
console.log(err); // 出错了
throw '二次出错';
}).catch(err => {
console.log(err); // 二次出错
});
为什么回调要用队列存储
一个 Promise 并不是只能被调用一次 then 方法,它可以被多次调用,对应多个回调函数。
正因为支持多次 then 调用,所以需要用「队列」(本质是数组,支持存储多个回调)来存放所有的回调,而不是只用一个变量存储单个回调。这也解释了为什么在.then时需要判断当前状态,而非直接push回调。
1. 一个 Promise 可以多次调用 .then()
平时开发中大多是「链式调用」(p.then().then()),但链式调用和「多次调用 then」是两回事:
- 「链式调用」:
p.then().then(),后面的then是上一个then返回的新 Promise的then,不是同一个 Promise 的 - 「多次调用」:
p.then()、p.then(),两个then都是同一个 Promise 实例的,这是完全合法的
举个原生 Promise 的例子,验证多次 then 调用:
const p = new Promise((resolve) => {
setTimeout(() => resolve('成功结果'), 1000);
});
// 第一个回调:同一个 p 的 then 调用
p.then((res) => {
console.log('第一个回调:', res);
});
// 第二个回调:同一个 p 的 then 调用
p.then((res) => {
console.log('第二个回调:', res);
});
// 第三个回调:同一个 p 的 then 调用
p.then((res) => {
console.log('第三个回调:', res);
});
执行结果(1 秒后):
第一个回调: 成功结果
第二个回调: 成功结果
第三个回调: 成功结果
可以看到,同一个 Promise 实例的多个 then 回调都被执行了,这就意味着 Promise 必须能存储多个回调函数,而不是单个。
2. 为什么要用队列(数组)
- 首先,「队列」在这里是广义的,我们实现时用的是普通数组(
onFulfilledCallbacks = []),它的核心作用是「存储多个回调」,满足多次then调用的需求。 - 其次,从执行顺序上看,多个
then回调的执行顺序是「先注册,先执行」(符合队列「先进先出(FIFO)」的特性),用数组存储并按顺序遍历执行,正好符合这个需求。
比如上面的例子,三个 then 回调会按注册顺序,依次被存入 onFulfilledCallbacks 数组:
onFulfilledCallbacks = [
() => { console.log('第一个回调:', res); },
() => { console.log('第二个回调:', res); },
() => { console.log('第三个回调:', res); }
]
当 resolve 执行时,遍历这个数组,按顺序执行每个回调,正好实现「先注册,先执行」,符合开发者的预期。
如果不用数组,只用一个变量存储单个回调,那么后面注册的回调会覆盖前面的,最终只有最后一个 then 回调被执行,这就不符合 Promise 的规范了。
3. 用我们手写的 MyPromise 验证
用之前的 MyPromise 运行上面的多次 then 例子,结果和原生 Promise 一致:
const p = new MyPromise((resolve) => {
setTimeout(() => resolve('成功结果'), 1000);
});
p.then((res) => { console.log('第一个回调:', res); });
p.then((res) => { console.log('第二个回调:', res); });
p.then((res) => { console.log('第三个回调:', res); });
执行结果(1 秒后):
第一个回调: 成功结果
第二个回调: 成功结果
第三个回调: 成功结果
这说明我们用数组(队列)存储回调的设计,是符合 Promise 规范的。
总结
手写 Promise 的核心关键点回顾:
- 状态不可逆:只有
pending能转为fulfilled/rejected,状态一旦改变就固定。 - 异步回调队列:处理异步执行器场景,
pending状态时缓存回调,状态改变后批量执行。 - 链式调用核心:
then方法返回新 Promise,通过resolvePromise处理回调返回值,透传结果或异常。 - 异步执行:回调必须异步触发(用
setTimeout模拟微任务,符合 Promise/A+ 规范)。