手写Promise完整实现(附源码+面试题)
Promise 是前端异步编程的基石,也是面试中最高频、最容易拉开差距的考点。很多人会用,但一让手写就慌;能写出来,又讲不清原理。
今天这篇,从 0 到 1 实现一个完整 Promise,代码带详细注释,可直接复制运行,最后附上高频面试题答案,面试前看这一篇就够。
适合:准备面试的前端、想夯实基础的同学、零基础进阶。
一、先明确Promise的核心特性
二、从0到1手写Promise(完整源码+注释)
1. 定义Promise类,初始化状态和回调
// 定义Promise的三种状态(常量,避免魔法值)const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';// 手写Promise类class MyPromise {// 构造函数,接收一个执行器函数(executor),立即执行constructor(executor) {try {// 执行器函数接收两个参数:resolve和reject方法executor(this.resolve, this.reject);} catch (error) {// 执行器执行过程中抛出错误,直接触发rejectthis.reject(error);}}// 初始状态为pendingstatus = PENDING;// 存储成功的结果value = undefined;// 存储失败的原因reason = undefined;// 存储成功的回调函数(用于异步场景,状态改变时执行)onFulfilledCallbacks = [];// 存储失败的回调函数(用于异步场景,状态改变时执行)onRejectedCallbacks = [];// 成功方法:将状态改为fulfilled,保存成功结果,执行所有成功回调resolve = (value) => {// 状态一旦改变,不能再修改(只有pending状态能改为fulfilled)if (this.status !== PENDING) return;// 改变状态为成功this.status = FULFILLED;// 保存成功结果this.value = value;// 执行所有缓存的成功回调(异步场景下,回调会先存入数组)while (this.onFulfilledCallbacks.length) {// 取出回调并执行(shift()取出第一个,确保顺序执行)this.onFulfilledCallbacks.shift()(value);}};// 失败方法:将状态改为rejected,保存失败原因,执行所有失败回调reject = (reason) => {// 状态一旦改变,不能再修改(只有pending状态能改为rejected)if (this.status !== PENDING) return;// 改变状态为失败this.status = REJECTED;// 保存失败原因this.reason = reason;// 执行所有缓存的失败回调while (this.onRejectedCallbacks.length) {this.onRejectedCallbacks.shift()(reason);}};}
2. 实现then方法(核心,支持链式调用)
// 实现then方法,接收两个可选参数:成功回调、失败回调then(onFulfilled, onRejected) {// 处理回调参数默认值:如果没传成功回调,直接返回结果;没传失败回调,直接抛出错误onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason };// 返回一个新的Promise,实现链式调用const newPromise = new MyPromise((resolve, reject) => {// 1. 当状态为fulfilled时,执行成功回调if (this.status === FULFILLED) {// 用setTimeout模拟异步(符合Promise的异步特性,即使状态已确定,回调也会异步执行)setTimeout(() => {try {// 执行成功回调,获取回调返回值const result = onFulfilled(this.value);// 关键:判断回调返回值是否为Promise,如果是,等待其状态改变;否则直接resolvethis.resolvePromise(newPromise, result, resolve, reject);} catch (error) {// 回调执行过程中抛出错误,触发新Promise的rejectreject(error);}}, 0);}// 2. 当状态为rejected时,执行失败回调if (this.status === REJECTED) {setTimeout(() => {try {const result = onRejected(this.reason);this.resolvePromise(newPromise, result, resolve, reject);} catch (error) {reject(error);}}, 0);}// 3. 当状态为pending时(异步场景,如定时器),将回调存入数组缓存if (this.status === PENDING) {// 缓存成功回调,后续状态改变时执行this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {const result = onFulfilled(this.value);this.resolvePromise(newPromise, result, resolve, reject);} catch (error) {reject(error);}}, 0);});// 缓存失败回调this.onRejectedCallbacks.push(() => {setTimeout(() => {try {const result = onRejected(this.reason);this.resolvePromise(newPromise, result, resolve, reject);} catch (error) {reject(error);}}, 0);});}});// 返回新的Promise,支持链式调用return newPromise;}// 辅助方法:处理then回调返回值,判断是否为Promise,实现链式调用的核心逻辑resolvePromise(newPromise, result, resolve, reject) {// 避免循环引用(如果回调返回的Promise就是当前新创建的Promise,直接报错)if (newPromise === result) {return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));}// 处理 thenable(包括 Promise 和其他有 then 方法的对象)if (result !== null && (typeof result === 'object' || typeof result === 'function')) {try {const then = result.then;if (typeof then === 'function') {// 是 thenable,异步调用 thenthen.call(result, resolve, reject);return;}} catch (error) {return reject(error);}}resolve(result);}
3. 实现catch、finally、resolve、reject方法
// 实现catch方法:捕获Promise的失败状态,本质是then方法的语法糖(只传失败回调)catch(onRejected) {return this.then(undefined, onRejected);}// 实现finally方法:无论Promise成功还是失败,都会执行(不接收参数,不改变Promise结果)finally(callback) {return this.then(// 成功时,先执行callback,再返回结果(value) => MyPromise.resolve(callback()).then(() => value),// 失败时,先执行callback,再抛出错误(reason) => MyPromise.resolve(callback()).then(() => { throw reason; }));}// 实现静态resolve方法:直接返回一个成功状态的Promisestatic resolve(value) {// 如果传入的是Promise,直接返回;否则返回一个成功的Promiseif (value instanceof MyPromise) return value;return new MyPromise((resolve) => resolve(value));}// 实现静态reject方法:直接返回一个失败状态的Promisestatic reject(reason) {return new MyPromise((resolve, reject) => reject(reason));}
4. 完整源码(可直接复制运行)
// 完整手写Promise源码(可直接复制运行)const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';class MyPromise {constructor(executor) {try {executor(this.resolve, this.reject);} catch (error) {this.reject(error);}}status = PENDING;value = undefined;reason = undefined;onFulfilledCallbacks = [];onRejectedCallbacks = [];resolve = (value) => {if (this.status !== PENDING) return;this.status = FULFILLED;this.value = value;while (this.onFulfilledCallbacks.length) {this.onFulfilledCallbacks.shift()(value);}};reject = (reason) => {if (this.status !== PENDING) return;this.status = REJECTED;this.reason = reason;while (this.onRejectedCallbacks.length) {this.onRejectedCallbacks.shift()(reason);}};then(onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason };const newPromise = new MyPromise((resolve, reject) => {if (this.status === FULFILLED) {setTimeout(() => {try {const result = onFulfilled(this.value);this.resolvePromise(newPromise, result, resolve, reject);} catch (error) {reject(error);}}, 0);}if (this.status === REJECTED) {setTimeout(() => {try {const result = onRejected(this.reason);this.resolvePromise(newPromise, result, resolve, reject);} catch (error) {reject(error);}}, 0);}if (this.status === PENDING) {this.onFulfilledCallbacks.push(() => {setTimeout(() => {try {const result = onFulfilled(this.value);this.resolvePromise(newPromise, result, resolve, reject);} catch (error) {reject(error);}}, 0);});this.onRejectedCallbacks.push(() => {setTimeout(() => {try {const result = onRejected(this.reason);this.resolvePromise(newPromise, result, resolve, reject);} catch (error) {reject(error);}}, 0);});}});return newPromise;}resolvePromise(newPromise, result, resolve, reject) {if (newPromise === result) {return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));}if (result !== null && (typeof result === 'object' || typeof result === 'function')) {try {const then = result.then;if (typeof then === 'function') {// 是 thenable,异步调用 thenthen.call(result, resolve, reject);return;}} catch (error) {return reject(error);}}resolve(result);}catch(onRejected) {return this.then(undefined, onRejected);}finally(callback) {return this.then((value) => MyPromise.resolve(callback()).then(() => value),(reason) => MyPromise.resolve(callback()).then(() => { throw reason; }));}static resolve(value) {if (value instanceof MyPromise) return value;return new MyPromise((resolve) => resolve(value));}static reject(reason) {return new MyPromise((resolve, reject) => reject(reason));}}// 测试代码(可删除,用于验证功能)// MyPromise.resolve('成功').then(res => console.log(res)).catch(err => console.log(err)).finally(() => console.log('执行完毕'));// new MyPromise((resolve, reject) => {// setTimeout(() => resolve('异步成功'), 1000);// }).then(res => res + '123').then(res => console.log(res));
三、测试验证(确保代码可用)
// 测试1:基本成功场景new MyPromise((resolve, reject) => {resolve('测试成功');}).then(res => {console.log(res); // 输出:测试成功}).finally(() => {console.log('测试1执行完毕'); // 输出:测试1执行完毕});// 测试2:异步场景(定时器)new MyPromise((resolve, reject) => {setTimeout(() => {resolve('异步测试成功');}, 1000);}).then(res => {return res + ',链式调用';}).then(res => {console.log(res); // 1秒后输出:异步测试成功,链式调用}).catch(err => {console.log(err);});// 测试3:失败场景new MyPromise((resolve, reject) => {reject('测试失败');}).catch(err => {console.log(err); // 输出:测试失败}).finally(() => {console.log('测试3执行完毕'); // 输出:测试3执行完毕});// 测试4:静态方法MyPromise.resolve('静态resolve成功').then(res => console.log(res)); // 输出:静态resolve成功MyPromise.reject('静态reject失败').catch(err => console.log(err)); // 输出:静态reject失败
四、高频面试题(手写Promise必问)
问:什么是 thenable?为什么要处理 thenable 而不是只判断 Promise 实例?
答:thenable 是指有 then 方法的对象或函数。Promise/A+ 规范要求兼容所有 thenable,而不只是原生 Promise 实例,这是鸭子类型的体现,确保不同库之间的 Promise 可以互操作。
五、总结
如果你觉得文章内容有用,请记得点赞、收藏、转发。
点击下面的公众号名片直接关注!
夜雨聆风