从零到一:手写Promise全流程解析与实现
2025.09.19 12:47浏览量:0简介:本文详细解析如何从0开始手写一个Promise,涵盖状态管理、链式调用、异常处理等核心机制,适合想要深入理解Promise原理的开发者。
从零到一:手写Promise全流程解析与实现
1. 引言:为什么需要手写Promise?
Promise是JavaScript异步编程的核心,但原生Promise的实现隐藏了诸多细节。手写Promise不仅能帮助开发者深入理解其工作原理,还能在以下场景中发挥作用:
- 面试中考察对异步编程的理解
- 定制化Promise行为(如添加日志、超时控制)
- 在无原生Promise的环境中(如旧浏览器)实现兼容
- 教学目的,帮助他人理解Promise机制
2. Promise核心概念回顾
在开始实现前,我们需要明确Promise的几个核心特性:
- 三种状态:pending(待定)、fulfilled(已兑现)、rejected(已拒绝)
- 状态不可逆:一旦从pending变为fulfilled或rejected,就不能再改变
- then方法:支持链式调用,接收onFulfilled和onRejected两个回调
- 异步执行:resolve/reject的回调会被放入微任务队列
3. 基础结构搭建
3.1 构造函数实现
class MyPromise {
constructor(executor) {
// 初始化状态和结果
this.state = 'pending'; // pending, fulfilled, rejected
this.result = undefined;
// 存储回调队列(用于then的链式调用)
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// resolve函数
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.result = value;
// 执行所有成功的回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
// reject函数
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.result = reason;
// 执行所有失败的回调
this.onRejectedCallbacks.forEach(fn => fn());
}
};
// 执行executor,捕获可能的异常
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
}
3.2 关键点解析
- 状态管理:使用
state
变量严格跟踪Promise状态 - 回调队列:存储then方法注册的回调,在状态变更时执行
- 异常处理:在executor执行时捕获同步错误
4. then方法实现
then方法是Promise的核心,它需要处理:
- 异步执行回调
- 链式调用(返回新的Promise)
- 值穿透(如果then的参数不是函数)
then(onFulfilled, onRejected) {
// 处理值穿透:如果参数不是函数,则提供一个默认函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
// 根据当前状态决定如何处理
if (this.state === 'fulfilled') {
// 使用setTimeout模拟微任务(实际浏览器中使用queueMicrotask)
setTimeout(() => {
try {
const x = onFulfilled(this.result);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.result);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
} else if (this.state === 'pending') {
// 如果是pending状态,将回调存入队列
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.result);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.result);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
}, 0);
});
}
});
return promise2;
}
5. resolvePromise:处理链式调用的核心
这是Promise实现中最复杂的部分,需要处理:
- 返回值是Promise的情况
- 循环引用检测
- 抛出异常的处理
function resolvePromise(promise2, x, resolve, reject) {
// 防止循环引用
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called = false;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
// x是一个Promise(或类Promise对象)
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// x是一个普通对象
resolve(x);
}
} catch (err) {
if (called) return;
called = true;
reject(err);
}
} else {
// x是普通值
resolve(x);
}
}
6. 静态方法实现
6.1 Promise.resolve
static resolve(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise(resolve => resolve(value));
}
6.2 Promise.reject
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
6.3 Promise.all
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
});
});
}
6.4 Promise.race
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
}
7. 微任务队列的模拟实现
浏览器中使用queueMicrotask
,Node.js中使用process.nextTick
。这里我们简单模拟:
// 在MyPromise类中添加
static _microtaskQueue = [];
static _isFlushing = false;
static _flush() {
if (this._isFlushing) return;
this._isFlushing = true;
while (this._microtaskQueue.length) {
const callback = this._microtaskQueue.shift();
callback();
}
this._isFlushing = false;
}
static queueMicrotask(callback) {
this._microtaskQueue.push(callback);
if (!this._isFlushing) {
setTimeout(this._flush.bind(this), 0);
}
}
然后在then方法中使用:
// 替换之前的setTimeout
MyPromise.queueMicrotask(() => {
try {
const x = onFulfilled(this.result);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
8. 完整实现代码
将以上所有部分组合起来,我们得到一个完整的Promise实现:
class MyPromise {
static _microtaskQueue = [];
static _isFlushing = false;
static _flush() {
if (this._isFlushing) return;
this._isFlushing = true;
while (this._microtaskQueue.length) {
const callback = this._microtaskQueue.shift();
callback();
}
this._isFlushing = false;
}
static queueMicrotask(callback) {
this._microtaskQueue.push(callback);
if (!this._isFlushing) {
setTimeout(this._flush.bind(this), 0);
}
}
static resolve(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise(resolve => resolve(value));
}
static reject(reason) {
return new MyPromise((_, reject) => reject(reason));
}
static all(promises) {
return new MyPromise((resolve, reject) => {
const results = [];
let count = 0;
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
count++;
if (count === promises.length) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
});
});
}
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
}
constructor(executor) {
this.state = 'pending';
this.result = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.result = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.result = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
const handleFulfilled = () => {
MyPromise.queueMicrotask(() => {
try {
const x = onFulfilled(this.result);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
};
const handleRejected = () => {
MyPromise.queueMicrotask(() => {
try {
const x = onRejected(this.result);
resolvePromise(promise2, x, resolve, reject);
} catch (err) {
reject(err);
}
});
};
if (this.state === 'fulfilled') {
handleFulfilled();
} else if (this.state === 'rejected') {
handleRejected();
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason; })
);
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called = false;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (err) {
if (called) return;
called = true;
reject(err);
}
} else {
resolve(x);
}
}
9. 测试用例
// 测试1:基本用法
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve('成功'), 1000);
});
promise.then(
value => console.log(value), // 应该输出"成功"
reason => console.error(reason)
);
// 测试2:链式调用
MyPromise.resolve(1)
.then(x => x + 1)
.then(x => x * 2)
.then(x => console.log(x)); // 应该输出4
// 测试3:Promise.all
const p1 = MyPromise.resolve(1);
const p2 = MyPromise.resolve(2);
const p3 = MyPromise.resolve(3);
MyPromise.all([p1, p2, p3]).then(values => {
console.log(values); // 应该输出[1, 2, 3]
});
// 测试4:异常处理
new MyPromise((_, reject) => reject('错误'))
.catch(err => console.error(err)); // 应该输出"错误"
10. 总结与扩展
通过手写Promise,我们深入理解了以下核心概念:
- 状态管理:Promise的三种状态及其转换规则
- 异步执行:微任务队列的实现机制
- 链式调用:then方法的返回值处理
- 异常处理:同步和异步错误的捕获
10.1 进一步优化方向
- 性能优化:减少微任务队列的开销
- 兼容性增强:支持更老的浏览器环境
- 功能扩展:添加finally、allSettled等方法
- 调试支持:添加堆栈跟踪功能
10.2 实际应用建议
- 在学习阶段,可以对比原生Promise的实现差异
- 在生产环境中,除非有特殊需求,否则建议使用原生Promise
- 可以通过扩展MyPromise类来添加自定义功能
手写Promise不仅是一次技术挑战,更是对JavaScript异步编程模式的深度理解。通过这个过程,开发者能够更好地掌握Promise的工作原理,从而写出更健壮、更高效的异步代码。
发表评论
登录后可评论,请前往 登录 或 注册