手写 Promise 全流程:从零实现符合 Promises/A+ 规范的库

异步编程是 JavaScript 的核心难点之一,而 Promise 作为现代异步编程的基石,深刻影响着 async/await、fetch 等 API 的设计。理解 Promise 的最好方式,就是亲手实现一个符合 Promises/A+ 规范的库。本文将带你从零开始,逐步构建一个可用的 Promise 实现。

为什么需要 Promises/A+ 规范

在开始编码之前,我们需要明确目标。Promises/A+ 规范定义了 then 方法的行为标准,包括:

  • Promise 的三种状态:pending、fulfilled、rejected
  • 状态一旦改变就不可逆转
  • then 方法必须返回一个新的 Promise
  • 回调必须异步执行
  • 值穿透与错误捕获机制

遵循这套规范意味着我们的实现可以与其他 Promise 库互操作,也能通过官方测试套件的验证。

基础骨架:状态管理与构造函数

首先定义 Promise 的三种状态和基本结构:

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state !== PENDING) return;
      this.state = FULFILLED;
      this.value = value;
      this.onFulfilledCallbacks.forEach(fn => fn());
    };

    const reject = (reason) => {
      if (this.state !== PENDING) return;
      this.state = REJECTED;
      this.reason = reason;
      this.onRejectedCallbacks.forEach(fn => fn());
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
}

这里用两个回调数组保存 pending 状态下的订阅者,当状态改变时统一触发。try/catch 确保执行器内部抛出的异常能被正确捕获。

实现 then 方法:核心难点

then 方法是整个规范的核心,需要处理同步值、异步回调、链式调用和 thenable 对象等多种情况:

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 = () => {
      queueMicrotask(() => {
        try {
          const x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    };

    const handleRejected = () => {
      queueMicrotask(() => {
        try {
          const x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        } catch (err) {
          reject(err);
        }
      });
    };

    if (this.state === FULFILLED) {
      handleFulfilled();
    } else if (this.state === REJECTED) {
      handleRejected();
    } else {
      this.onFulfilledCallbacks.push(handleFulfilled);
      this.onRejectedCallbacks.push(handleRejected);
    }
  });

  return promise2;
}

几个关键设计点:

  1. 值穿透:当 onFulfilled 不是函数时,默认传递值;当 onRejected 不是函数时,默认抛出错误。
  2. 异步执行:使用 queueMicrotask 保证回调在微任务中执行,符合规范要求。
  3. 返回新 Promise:每次调用 then 都返回新的 Promise,支持链式调用。

resolvePromise:处理返回值

这是最复杂的部分,需要判断返回值 x 的各种情况:

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }

  if (x instanceof MyPromise) {
    x.then(resolve, reject);
    return;
  }

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let called = false;
    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);
  }
}

这段代码处理了三种情况:循环引用检测、Promise 实例的直接传递、以及 thenable 对象的递归解析。called 标志位确保 resolve/reject 只被调用一次。

静态方法与原型方法

为了让实现更完整,我们补充常用的静态方法:

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) return resolve(results);
    promises.forEach((p, i) => {
      MyPromise.resolve(p).then(value => {
        results[i] = value;
        if (++count === promises.length) resolve(results);
      }, reject);
    });
  });
}

以及实例方法 catchfinally

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; })
  );
}

验证与测试

完成实现后,可以用 promises-aplus-tests 套件验证:

npm install promises-aplus-tests --save-dev

配置适配器后运行测试,全部通过说明实现符合规范。常见的坑包括:回调未异步执行、循环引用未检测、thenable 的 then 方法被调用多次等。

总结

手写 Promise 的过程让我们深入理解了异步编程的底层机制。核心要点可以归纳为:

  • 状态机:pending → fulfilled/rejected 的单向转换
  • 发布订阅:pending 状态下收集回调,状态改变后统一触发
  • 微任务调度:保证回调异步执行,避免阻塞主线程
  • 递归解析:通过 resolvePromise 处理各种返回值类型

掌握这些原理后,阅读 async/await 的编译产物或调试异步代码都会更加得心应手。建议读者在理解的基础上,尝试自己动手实现一遍,并跑通官方测试套件,这比单纯阅读代码的收获要大得多。

未经允许不得转载:任鹏个人博客 » 手写 Promise 全流程:从零实现符合 Promises/A+ 规范的库

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏