创见博客
手写实现Promise
七崽爱吃小饼干2026/01/30阅读 1专栏 JavaScript

首先,我们先明确 Promise 的核心特性,再分步实现:

  1. Promise 有三种状态:pending(等待)、fulfilled(成功)、rejected(失败)
  2. 状态只能从 pending 转为 fulfilled 或 pending 转为 rejected,一旦改变就无法再修改(不可逆)
  3. 每个 Promise 实例都有 then 方法,用于注册成功/失败回调
  4. 回调函数是异步执行的(微任务,这里用 setTimeout 模拟,简化实现)
  5. 支持 then 方法的链式调用(核心是 then 方法返回一个新的 Promise)

分步实现手写 Promise

第一步:实现基础结构(状态、构造函数)

先搭建 Promise 的基本骨架,处理状态初始化和执行器函数(executor)。

javascript
// 定义三种状态常量,避免魔法字符串
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

javascript
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->执行回调队列中的回调函数

javascript
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 模拟微任务)。

javascript
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 方法:

javascript
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

我们用几个场景测试一下实现效果,验证是否符合预期:

javascript
// 测试 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 调用:

javascript
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 一致:

javascript
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 的核心关键点回顾:

  1. 状态不可逆:只有 pending 能转为 fulfilled/rejected,状态一旦改变就固定。
  2. 异步回调队列:处理异步执行器场景,pending 状态时缓存回调,状态改变后批量执行。
  3. 链式调用核心:then 方法返回新 Promise,通过 resolvePromise 处理回调返回值,透传结果或异常。
  4. 异步执行:回调必须异步触发(用 setTimeout 模拟微任务,符合 Promise/A+ 规范)。
评论
0/100