摘要: Promise 是现代 JavaScript 异步编程的基石。你是否曾好奇过 Promise 内部是如何工作的?本文将带你从零开始,通过一个完整的手写实现,深入理解 Promise 的核心原理。我们不仅会实现基本功能,还会探讨状态机、微任务、链式调用等关键概念,最终写出一个符合 Promise/A+ 规范的 MyPromise


引言:为什么我们要手动实现 Promise?

在日常开发中,我们频繁使用 Promiseasync/await 来处理异步操作。但大多数时候,我们只是“使用者”,对它的内部机制知之甚少。

手动实现 Promise 有以下几个好处:

  1. 加深理解: 理解 Promise 的状态流转、链式调用、错误处理等机制。
  2. 面试加分: 这是前端面试中的经典手写题,能体现你对异步编程的掌握程度。
  3. 提升 Debug 能力:Promise 出现问题时,你能更快定位原因。

接下来,就让我们一起揭开 Promise 的神秘面纱!


核心设计:状态机 + 任务队列

Promise 的核心是一个状态机。它有三种状态:

  • pending(等待中): 初始状态,既没有被 fulfilled,也没有被 rejected。
  • fulfilled(已成功): 意味着操作成功完成。
  • rejected(已失败): 意味着操作失败。

关键规则:

  • 状态只能从 pending 变为 fulfilledrejected
  • 一旦状态改变,就不可逆

基于此,我们定义 MyPromise 类的基本结构:

class MyPromise {
    // 1. 定义初始状态和结果
    state = 'pending';
    result = null;
    handlers = []; // 存储 then 的回调

    constructor(execute) {
        // 2. 执行传入的执行器函数
        const reject = (data) => {
            this.changeState('rejected', data);
        };
        const resolve = (data) => {
            this.changeState('fulfilled', data);
        };

        try {
            execute(resolve, reject);
        } catch (error) {
            reject(error); // 捕获执行器中的同步错误
        }
    }

    // 3. 改变状态(核心方法)
    changeState(state, data) {
        if (this.state !== 'pending') return; // 状态不可逆
        this.state = state;
        this.result = data;
        this.run(); // 状态改变后,立即执行所有回调
    }
}

✅ 解析

  • stateresult 分别存储当前状态和结果值。
  • handlers 是一个数组,用于存放 then 方法注册的回调函数(我们稍后会详细设计)。
  • constructor 接收一个执行器函数 execute,并立即执行它,同时提供 resolvereject 函数。
  • changeState 是状态变更的唯一入口,确保状态只能从 pending 变更一次。

实现 then 方法:链式调用的基石

then 方法是 Promise 的灵魂,它允许我们注册成功和失败的回调,并支持链式调用。

then(onFulfilled, onRejected) {
    // 返回一个新的 Promise,实现链式调用
    return new MyPromise((resolve, reject) => {
        // 将回调和 resolve/reject 封装成任务
        this.handlers.push({
            onFulfilled,
            onRejected,
            resolve,
            reject,
        });
        // 状态改变后,run 会执行这些任务
        this.run();
    });
}

✅ 解析

  • then 必须返回一个新的 Promise,这样才能支持 .then().then() 的链式调用。
  • 我们将 onFulfilledonRejected 以及新 Promiseresolvereject 封装成一个“任务”对象,存入 handlers 队列。
  • 调用 this.run() 尝试执行这些任务。

执行回调:runrunOne 方法

Promise 状态改变时,我们需要执行所有已注册的回调。

run() {
    if (this.state === 'pending') return;

    // 使用 while 循环处理所有 handlers
    while (this.handlers.length) {
        const { onFulfilled, onRejected, resolve, reject } = this.handlers.shift(); // 注意:这里用 shift 更符合队列逻辑
        this.runOne(onFulfilled, onRejected, resolve, reject);
    }
}

runOne(onFulfilled, onRejected, resolve, reject) {
    // 使用微任务包裹,确保异步执行
    queueMicrotask(() => {
        // 获取当前状态对应的回调
        const callback = this.state === 'fulfilled' ? onFulfilled : onRejected;

        // 如果回调不是函数,直接透传结果(实现值穿透)
        if (typeof callback !== 'function') {
            const settled = this.state === 'fulfilled' ? resolve : reject;
            settled(this.result);
            return;
        }

        try {
            // 执行回调函数
            const data = callback(this.result);

            // 处理返回值:如果是 Promise,则链式传递
            if (this.isPromise(data)) {
                data.then(resolve, reject);
            } else {
                resolve(data); // 普通值,直接 resolve
            }
        } catch (error) {
            reject(error); // 捕获回调中的错误
        }
    });
}

✅ 解析

  • run 方法在状态改变后被调用,它会遍历 handlers 队列,逐个执行 runOne
  • runOne 使用 queueMicrotask(或 setTimeout(fn, 0))将回调放入微任务队列,确保异步执行,符合 Promise 的事件循环机制。
  • 值穿透: 如果 onFulfilledonRejected 不是函数(比如传了 null),则直接将结果透传给下一个 then
  • 处理返回值: 这是 Promise 链式调用的关键。如果回调返回的是一个 Promise,我们就用它的结果来 resolvereject 新的 Promise;否则,直接用返回值 resolve

工具方法:isPromisemicroTask

isPromise(prom) {
    if (prom && (typeof prom === 'object' || typeof prom === 'function')) {
        return typeof prom.then === 'function'; // 检查是否有 then 方法
    }
    return false;
}

isPromise 用于判断一个值是否是 Promise(或 thenable 对象),这是实现 Promise.resolve 和处理链式调用的基础。

注意: 你的代码中 microTask 使用了 setTimeout,这实际上是宏任务。真正的 Promise 使用的是微任务。在现代浏览器中,应使用 queueMicrotaskMutationObserver 来模拟微任务。为了更准确,建议替换为:

microTask(callback) {
    queueMicrotask(callback);
}
// 或者兼容性更好的实现
// if (queueMicrotask) { ... } else { setTimeout(callback, 0); }

测试我们的 MyPromise

让我们用一个经典的例子来测试:

const p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功');
    }, 300);
});

// 链式调用
p.then(res => {
    console.log('res-1', res); // res-1 成功
    return '这是第二个成功'; // 普通值
}).then(res => {
    console.log('res-2', res); // res-2 这是第二个成功
});

// 多个 then 监听同一个 Promise
p.then(
    res => {
        console.log('res-3', res); // res-3 成功
    },
    err => {
        console.log('err', err);
    }
);

✅ 预期输出:

res-1 成功
res-3 成功
res-2 这是第二个成功

注意:由于微任务的调度,res-1res-3 的顺序取决于 handlers 的处理顺序(push + shift 是 FIFO 队列)。


完整代码

	class MyPromise {
				state = 'pending';
				result = null;
				handlers = [];

				constructor(execute) {
					const reject = data => {
						this.changeState('rejected', data);
					};
					const resolve = data => {
						this.changeState('fulfilled', data);
					};
					try {
						execute(resolve, reject);
					} catch (error) {
						reject(error);
					}
				}

				changeState(state, data) {
					if (this.state !== 'pending') return;
					this.state = state;
					this.result = data;
					this.run();
				}

				isPromise(prom) {
					if (prom && (typeof prom === 'function' || typeof prom === 'object')) {
						return prom?.then;
					}
					return false;
				}

				microTask(callBack) {
					setTimeout(callBack, 0);
				}

				runCOne(callBack, resolve, reject) {
					microTask(() => {
						if (typeof CallBack !== 'function') {
							const settled = this.state === 'fulfilled' ? resolve : reject;
							settled(this.result);
							return;
						}
						try {
							const data = callBack(this.result);
							if (this.isPromise(data)) {
								data.then(resolve, reject);
							} else {
								resolve(data);
							}
						} catch (error) {
							reject(error);
						}
					});
				}

				run() {
					if (this.state === 'pending') {
						return;
					}
					while (this.handlers.length) {
						const { onFulfilled, onRejected, resolve, reject } = this.handlers.pop();
						if (this.state === 'fulfilled') {
							onFulfilled(this.result);
						} else if (this.state === 'rejected') {
							onRejected(this.result);
						}
					}
				}

				then(onFulfilled, onRejected, resolve, reject) {
					return new Promise((resolve, reject) => {
						this.handlers.push({
							onFulfilled,
							onRejected,
							resolve,
							reject,
						});
						this.run();
					});
				}
			}

			const p = new Promise((resolve, reject) => {
				setTimeout(() => {
					resolve('成功');
				}, 300);
			});

			p.then(res => {
				console.log('res-1', res);
				return new Promise((resolve, reject) => {
					resolve('这是第二个成功');
				});
			}).then(res => {
				console.log('res-2', res);
			});

总结

通过手动实现 MyPromise,我们深入理解了 Promise 的核心机制:

  • 状态机: pendingfulfilled/rejected 的单向流转。
  • 任务队列: handlers 存储异步回调,run 方法触发执行。
  • 链式调用: then 返回新 Promise,实现 .then().then()
  • 值穿透与错误处理: 确保链的健壮性。
  • 微任务: 保证回调在正确的时间点异步执行。

虽然一个完整的 Promise 还有 catchfinallyPromise.resolvePromise.rejectPromise.all 等方法,但掌握了 then 的实现,其他方法都迎刃而解。


下一步

  • 实现 catch 方法:catch(onRejected) 等价于 then(null, onRejected)
  • 实现静态方法 Promise.resolve()Promise.reject()
  • 实现 Promise.allPromise.race 等组合方法。

手动实现 Promise 不仅是一次代码练习,更是一次对 JavaScript 异步编程思想的深刻探索。希望这篇博客能帮助你彻底掌握 Promise 的精髓!

上班心得

————————————————
上班真的好开心,需求bug来不停。

产品测试来回找,后端交互想上刑。

会议不停满楼跑,文档笔记要分清。

摸鱼睡觉一时爽,提测发布胆惊心。

大佬思绪跟得上,想法创意实践灵。

日常积累很重要,自我提高才算行。

暮然回首学生时,半载已过还未明。

处处少年何模样?如今胡须满颔停!
————————————————

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐