手写一个符合 Promise/A+ 规范的 Promise(附完整代码)

手写一个符合 Promise/A+ 规范的 Promise(附完整代码)

本文将带你一步步实现一个可用的 Promise,包含状态管理、链式调用、异步微任务、错误穿透等核心特性。

一、基础结构:状态与 resolve/reject

Promise 的核心是状态机。我们定义三种状态:pendingfulfilledrejected。初始为 pending,一旦改变就不能再次变化。

javascript 复制代码
class MyPromise {
  constructor(fn) {
    this.state = 'pending';   // 当前状态
    this.value = null;        // 存储结果
    fn(this.resolve, this.reject);
  }

  resolve = (value) => {
    if (this.state !== 'pending') return; // 状态已定,不再执行
    this.value = value;
    this.state = 'fulfilled';
  }

  reject = (value) => {
    if (this.state !== 'pending') return;
    this.value = value;
    this.state = 'rejected';
  }
}

注意resolvereject 使用箭头函数定义,确保 this 指向实例,避免作为回调传递时丢失。

二、实现 then 的基本要求

then 方法必须返回一个新的 Promise,并且其回调要在微任务中执行(异步)。

javascript 复制代码
then(onFulfilled) {
  const nextPromise = new MyPromise((resolve, reject) => {
    if (this.state === 'fulfilled') {
      queueMicrotask(() => {
        const res = onFulfilled(this.value);
        resolve(res);
      });
    }
  });
  return nextPromise;
}

此时仅处理了 fulfilled 状态,且没有存储回调。

三、支持异步:回调队列

如果 thenresolve 之前调用,需要把回调存起来,等 resolve 时再执行。

javascript 复制代码
constructor(fn) {
  // ...
  this.fulfilledCallback = [];   // 存储 then 的成功回调
  this.rejectedCallback = [];    // 存储 then 的失败回调
}

then(onFulfilled) {
  const nextPromise = new MyPromise((resolve, reject) => {
    if (this.state === 'pending') {
      this.fulfilledCallback.push(() => {
        const res = onFulfilled(this.value);
        resolve(res);
      });
    } else if (this.state === 'fulfilled') {
      queueMicrotask(() => {
        const res = onFulfilled(this.value);
        resolve(res);
      });
    }
  });
  return nextPromise;
}

resolve 中需要遍历并执行队列里的回调:

javascript 复制代码
resolve = (value) => {
  if (this.state !== 'pending') return;
  this.value = value;
  this.state = 'fulfilled';
  const callbacks = this.fulfilledCallback.slice(); // 复制后清空
  this.fulfilledCallback = [];
  for (let cb of callbacks) {
    queueMicrotask(() => cb(value));
  }
}

四、完善 then 方法:处理 onRejected 和状态分支

then 应接收两个参数 onFulfilledonRejected,根据当前状态决定是立即放入微任务,还是存入队列。

javascript 复制代码
then(onFulfilled, onRejected) {
  // 默认值处理(非函数时透传/抛错)
  if (typeof onFulfilled !== 'function') onFulfilled = value => value;
  if (typeof onRejected !== 'function') onRejected = error => { throw error };

  const nextPromise = new MyPromise((resolve, reject) => {
    if (this.state === 'pending') {
      this.fulfilledCallback.push(() => {
        this.executorTryAndCatch(onFulfilled, resolve, reject, this.value, nextPromise);
      });
      this.rejectedCallback.push(() => {
        this.executorTryAndCatch(onRejected, resolve, reject, this.value, nextPromise);
      });
    } else if (this.state === 'fulfilled') {
      queueMicrotask(() => {
        this.executorTryAndCatch(onFulfilled, resolve, reject, this.value, nextPromise);
      });
    } else {
      queueMicrotask(() => {
        this.executorTryAndCatch(onRejected, resolve, reject, this.value, nextPromise);
      });
    }
  });
  return nextPromise;
}

五、提取公共逻辑 executorTryAndCatch

上面代码中反复出现的 try-catch + 返回值解析可以提取成一个方法:

javascript 复制代码
executorTryAndCatch(onFunction, resolve, reject, value, nextPromise) {
  try {
    const res = onFunction(value);
    // 循环引用检测
    if (res === nextPromise) throw new TypeError('Chaining cycle error');
    // 如果返回的是 Promise,则等待其完成
    if (res instanceof MyPromise) res.then(resolve, reject);
    else resolve(res);
  } catch (err) {
    reject(err);
  }
}

这个方法负责:

  • 执行用户回调
  • 捕获异常并 reject
  • 处理返回值(普通值直接 resolve,Promise 则等待)

六、实现 catch 方法

catch 就是 then(null, onRejected) 的语法糖:

javascript 复制代码
catch(onRejected) {
  return this.then(null, onRejected);
}

七、最终完整代码与测试

所有特性整合后,我们的 MyPromise 代码如下:

javascript 复制代码
class MyPromise {
  constructor(fn) {
    this.state = 'pending';
    this.value = null;
    this.fulfilledCallback = [];
    this.rejectedCallback = [];
    fn(this.resolve, this.reject);
  }

  resolve = (value) => {
    if (this.state !== 'pending') return;
    this.value = value;
    this.state = 'fulfilled';
    const callbacks = this.fulfilledCallback.slice();
    this.fulfilledCallback = [];
    for (let cb of callbacks) queueMicrotask(() => cb(value));
  }

  reject = (value) => {
    if (this.state !== 'pending') return;
    this.value = value;
    this.state = 'rejected';
    const callbacks = this.rejectedCallback.slice();
    this.rejectedCallback = [];
    for (let cb of callbacks) queueMicrotask(() => cb(value));
  }

  then(onFulfilled, onRejected) {
    if (typeof onFulfilled !== 'function') onFulfilled = value => value;
    if (typeof onRejected !== 'function') onRejected = error => { throw error };
    const nextPromise = new MyPromise((resolve, reject) => {
      if (this.state === 'pending') {
        this.fulfilledCallback.push(() => {
          this.executorTryAndCatch(onFulfilled, resolve, reject, this.value, nextPromise);
        });
        this.rejectedCallback.push(() => {
          this.executorTryAndCatch(onRejected, resolve, reject, this.value, nextPromise);
        });
      } else if (this.state === 'fulfilled') {
        queueMicrotask(() => {
          this.executorTryAndCatch(onFulfilled, resolve, reject, this.value, nextPromise);
        });
      } else {
        queueMicrotask(() => {
          this.executorTryAndCatch(onRejected, resolve, reject, this.value, nextPromise);
        });
      }
    });
    return nextPromise;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  executorTryAndCatch(onFunction, resolve, reject, value, nextPromise) {
    try {
      const res = onFunction(value);
      if (res === nextPromise) throw new TypeError('Chaining cycle error');
      if (res instanceof MyPromise) res.then(resolve, reject);
      else resolve(res);
    } catch (err) {
      reject(err);
    }
  }
}

测试代码

javascript 复制代码
const a = new MyPromise((resolve, reject) => {
  reject(1);
});
const t = a.catch(res => {
  res++;
  console.log(res + "---------1");
  return res;
});
t.then(res => {
    res++;
    console.log(res + "---------2");
    return res;
  })
  .then(res => {
    res = res ** 2;
    console.log(res + "---------3");
    return res;
  })
  .then(res => {
    res = res * 2;
    console.log(res + "---------4");
    return res;
  });
console.log(1);

输出:

lua 复制代码
1
2---------1
3---------2
9---------3
18---------4

八、总结

我们一步步实现了一个符合 Promise 核心规范的类,关键点总结如下:

  1. 状态管理pendingfulfilled/rejected,不可逆。
  2. 回调队列 :解决异步 then 先于 resolve 的问题。
  3. 微任务 :使用 queueMicrotask 保证回调异步执行。
  4. 链式调用then 返回新 Promise,并递归解析返回值。
  5. 错误处理try/catch 捕获异常并 reject
  6. 错误穿透 :默认的 onRejected 会继续抛出错误。
  7. 循环引用检测 :避免 then 返回自己的 nextPromise 导致死循环。

这个手写实现虽然未涵盖 Promise.allracefinally 等静态方法,但已经揭示了 Promise 的核心原理。掌握它,你对异步编程的理解会更上一层楼。

相关推荐
用户938515635071 小时前
从模块化到 Prompt 工程:我用 Node.js + LLM 复刻了传统 NLP 的流程
javascript·人工智能·node.js
bonechips1 小时前
用 Prompt 做 NLP:从零搭建一个情感分析与信息提取系统
javascript
暗不需求1 小时前
从路虎汽车小程序看微信小程序开发的最佳实践
前端·javascript·微信小程序
用户059540174461 小时前
我把RAG对话记忆测试从手工用例改成ChromaDB自动化评估,Bug发现率翻了4倍
前端·css
东风破_1 小时前
用原型实现一个队列:JS 面向对象的"不走寻常路"
javascript
向日的葵0061 小时前
vue路由(二)
前端·javascript·vue.js·vue
姓王者1 小时前
解决QQ浏览器等魔改内核下SVG背景图颜色异常变白的问题 | 姓王者的博客
前端
ejinxian1 小时前
Angular v22 正式发布:Signal Forms、Angular Aria 和 AI 开发工具全面生产化
前端·javascript·angular.js
小小龙学IT1 小时前
Tauri:用 Web 技术构建桌面应用的新范式
前端