从零手写 Promise:状态机、链式调用与 then 的三大规范化难点

举报
yd_252100366 发表于 2026/10/18 12:00:17 2026/10/18
【摘要】 # 从零手写 Promise:状态机、链式调用与 then 的三大规范化难点> 摘要:先讲清 Promise 的三状态不可逆模型,再逐步写出最小可用版本,深入 then 链式调用的三层返回逻辑、thenable 吸收、循环引用检测与只能决议一次三个规范化难点,最后补齐 all/race/allSettled/any 四个静态方法。> 标签:javascript、前端## 一、三个状态与一条铁...

# 从零手写 Promise:状态机、链式调用与 then 的三大规范化难点

> 摘要:先讲清 Promise 的三状态不可逆模型,再逐步写出最小可用版本,深入 then 链式调用的三层返回逻辑、thenable 吸收、循环引用检测与只能决议一次三个规范化难点,最后补齐 all/race/allSettled/any 四个静态方法。
> 标签:javascript、前端

## 一、三个状态与一条铁律

Promise 只有三种状态:

- `pending`:初始态,既没成功也没失败
- `fulfilled`:成功,携带 value
- `rejected`:失败,携带 reason

铁律只有一条:**状态只能从 pending 单向流转到 fulfilled 或 rejected,且不可逆、不可二次变更**。后续所有实现细节都是为了守住这条规则。另外 Promise 构造函数里的执行器(executor)是同步执行的,只有 then 回调才是微任务——这是新手最常搞混的一点。

## 二、最小可用版本

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

class MyPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.handlers = []; // 暂存 pending 期间的 then 回调

    const resolve = (value) => {
      if (this.state !== PENDING) return;
      this.state = FULFILLED;
      this.value = value;
      this.handlers.forEach((h) => h.onFulfilled(value));
    };
    const reject = (reason) => {
      if (this.state !== PENDING) return;
      this.state = REJECTED;
      this.value = reason;
      this.handlers.forEach((h) => h.onRejected(reason));
    };

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

  then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handle = (cb, fallback) => {
        // 微任务:保证 then 回调异步执行
        queueMicrotask(() => {
          try {
            if (typeof cb !== 'function') return fallback(this.value);
            resolve(cb(this.value)); // 关键:把回调结果作为新 Promise 的值
          } catch (e) {
            reject(e);
          }
        });
      };
      if (this.state === FULFILLED) {
        handle(onFulfilled, resolve);
      } else if (this.state === REJECTED) {
        handle(onRejected, reject);
      } else {
        this.handlers.push({
          onFulfilled: () => handle(onFulfilled, resolve),
          onRejected: () => handle(onRejected, reject),
        });
      }
    });
  }
}
```

两个关键设计:**then 返回新 Promise**(不是 this),所以能链式调用;回调里抛错要 `reject` 而不是冒泡出去。

## 三、then 链式调用的三种返回值

`then` 的返回值决定下一个 Promise 的状态,这是规范里最绕的部分:

1. **返回普通值**:下一个 Promise 直接 fulfilled,value 就是该值
2. **返回 Promise/thenable**:下一个 Promise 等它决议,状态和值都跟它走(称为 thenable 吸收)
3. **抛错**:下一个 Promise rejected,reason 是错误对象

另外两个必须处理的细节:

- **onFulfilled 不是函数就透传**:`.then(null)` 应该把 value 原样带下去,否则链就断了
- **回调必须异步执行**:用 queueMicrotask 或 Promise.resolve().then 包一层,否则同步执行会导致 `.then` 里的代码在 resolve 之前跑完

## 四、三个规范化难点

### 1. thenable 吸收

判断"返回的是不是 thenable"不能只 `instanceof MyPromise`,原生 Promise、第三方库的 Promise 都要能吸收:

```js
if (result && (typeof result === 'object' || typeof result === 'function')) {
  const then = result.then;
  if (typeof then === 'function') {
    then.call(result, resolve, reject); // 用它的 then 接管
  }
}
```

### 2. 循环引用检测

```js
const p = MyPromise.resolve().then(() => p); // 自己等自己,必须 reject
```

规则:如果 `then` 回调返回的就是新 Promise 本身,直接 reject 一个 TypeError。实现里保存 `promise2` 的引用,比较 `result === promise2`。

### 3. 决议只能一次

thenable 是恶意/异常对象时可能同时调 resolve 和 reject 两次,必须加锁:

```js
let called = false;
const onceResolve = (v) => { if (!called) { called = true; resolve(v); } };
const onceReject = (r) => { if (!called) { called = true; reject(r); } };
```

这三条是手写 Promise 从"能跑"到"符合规范"的分水岭,面试官基本都在这里追问。

## 五、四个静态方法

```js
// all:全部成功才成功,一个失败立即失败
MyPromise.all = (list) => new MyPromise((resolve, reject) => {
  const results = new Array(list.length);
  let count = 0;
  if (list.length === 0) return resolve([]);
  list.forEach((item, i) => {
    MyPromise.resolve(item).then((v) => {
      results[i] = v;
      if (++count === list.length) resolve(results);
    }, reject);
  });
});

// race:谁先决议就用谁
MyPromise.race = (list) => new MyPromise((resolve, reject) => {
  list.forEach((item) => MyPromise.resolve(item).then(resolve, reject));
});

// allSettled:等全部结束,永不 reject
// any:第一个成功就成功,全失败才 reject(AggregateError)
```

注意 all 里必须用 `MyPromise.resolve(item)` 包装,保证非 Promise 值也能处理,且结果顺序按传入顺序而非完成顺序。

## 六、实践中的三条建议

1. **优先用原生 Promise**:手写版没有 V8 的性能优化,也没有 async_hooks 支持,只适合学习
2. **别在 then 里忘 return**:`.then(() => fetchA())` 不 return,外层拿不到结果,链会提前结束
3. **finally 不收值不改变状态**:它只用于清理,返回值被忽略(除非抛错覆盖)

## 七、总结

手写 Promise 的核心就三件事:状态机守住单向不可逆、then 返回新 Promise 支撑链式、决议函数做 thenable 吸收与循环引用防护。把这三块写对,其余静态方法都是组合。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。