前言
Promise 源码看了一百遍,不如自己写一遍。
相信很多前端同学都有过这样的经历:面试问手写 Promise,网上搜一搜 "Promise A+ 规范实现",然后对着代码 Copy 一遍,写完还是云里雾里 ------ 那些 .then、Promise.resolve、Promise.all 到底是怎么串起来的?
这篇文章不打算贴完整代码(GitHub 上已经够多了),而是换个方式:用一个最小、最简、最裸的 MiniPromise,带你从零理解 Promise 的设计思路。
1. 先想清楚:Promise 解决什么问题?
在 Promise 出现之前,我们用回调函数来处理异步:
javascript
fetchData(function(result) {
processResult(result, function(processed) {
saveData(processed, function() {
// ... 回调地狱
});
});
});
这叫 回调地狱 (Callback Hell),问题不仅仅是嵌套难读,更重要的是 错误处理分散、状态不可控。
Promise 的核心思路就两点:
- 状态机:pending → fulfilled / rejected,只能变一次
- 链式调用 :
.then()返回一个新的 Promise,实现"异步组合"
理解这两点,你就能自己动手写一个简化版 Promise。
2. MiniPromise 的核心结构
javascript
class MiniPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => this._resolve(value);
const reject = (reason) => this._reject(reason);
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
就这?一个类,三个属性,两个数组?
对,就是这么裸。Promise 本质就是一个状态容器 ,所有的魔法都在 _resolve 方法里。
3. 核心逻辑:状态流转 + 异步执行
javascript
_resolve(value) {
// 只能从 pending 变一次
if (this.state !== 'pending') return;
// 如果 value 是 Promise,需要"展开"
if (value instanceof MiniPromise) {
return value.then(this._resolve.bind(this), this._reject.bind(this));
}
this.state = 'fulfilled';
this.value = value;
// 异步执行回调 ------ 这就是 then 可以链式调用的关键
queueMicrotask(() => {
this.onFulfilledCallbacks.forEach(cb => cb(this.value));
});
}
等等,这里有个关键点:为什么要用 queueMicrotask?
因为 Promise 的设计原则是:then 的回调必须异步执行。这保证了执行顺序的可预测性,也是 Promise/A+ 规范的要求。
4. then 是怎么实现的?
javascript
then(onFulfilled, onRejected) {
// 返回一个新的 Promise,这就是链式调用的秘密
return new MiniPromise((resolve, reject) => {
const handleCallback = (callback, value) => {
try {
// 如果没有传回调,直接透传 value
const result = callback ? callback(value) : value;
resolve(result); // 关键:resolve 的是回调的返回值
} catch (err) {
reject(err);
}
};
if (this.state === 'fulfilled') {
// 异步执行,保持一致性
queueMicrotask(() => handleCallback(onFulfilled, this.value));
} else if (this.state === 'rejected') {
queueMicrotask(() => handleCallback(onRejected, this.value));
} else {
// pending 状态,先把回调存起来
this.onFulfilledCallbacks.push(() => handleCallback(onFulfilled, this.value));
this.onRejectedCallbacks.push(() => handleCallback(onRejected, this.value));
}
});
}
看到没?then 返回的是一个全新的 Promise,而不是直接返回结果。这个新 Promise 的 resolve 取决于回调函数的返回值------这,就是链式调用的本质。
5. 静态方法:Promise.resolve / Promise.reject
javascript
static resolve(value) {
if (value instanceof MiniPromise) return value;
return new MiniPromise(resolve => resolve(value));
}
static reject(reason) {
return new MiniPromise((_, reject) => reject(reason));
}
简单到不用解释。
6. Promise.all 怎么写?
javascript
static all(promises) {
return new MiniPromise((resolve, reject) => {
const results = [];
let completed = 0;
if (promises.length === 0) return resolve([]);
promises.forEach((p, i) => {
MiniPromise.resolve(p).then(val => {
results[i] = val;
completed++;
if (completed === promises.length) resolve(results);
}, reject);
});
});
}
核心就一个:遍历 + 计数 + 全部成功才 resolve。
7. 写完 MiniPromise,我学到了什么?
-
Promise 不是什么魔法:就是一个有状态管理的异步容器,外加一套回调收集 + 异步调度机制
-
链式调用的本质 :每个
.then()返回一个新 Promise,上一个 then 的返回值成为下一个 then 的输入 -
queueMicrotask 的作用:确保 then 的回调总是异步执行,这是 Promise 行为一致性的根基
-
Promise.resolve 的"递归展开":这是 Promise 最难理解的部分------如果 resolve 的是一个 Promise,需要等它完成后再 fulfill 当前 Promise
结语
手写一遍之后,再看 Promise.all、Promise.race、async/await,你会发现它们都是建立在同一套机制上的延伸。
源码不是魔法,原理才是。
完整代码我已经整理到 GitHub,有兴趣的同学可以跑跑测试:
GitHub 地址(可替换为你的仓库)