文章

手写Promise类深度解析

从零实现符合 Promise/A+ 规范的 Promise 类——状态机、then 链式调用、resolvePromise、微任务调度,逐行拆解。

手写Promise类深度解析

一句话概括

手写 Promise 不是炫技,是把异步编程的底层逻辑亲手摸一遍——状态机决定何时唤醒回调,then 返回新 Promise 决定链式如何连接,resolvePromise 决定返回值如何传递


核心知识点

1. 状态机 + 回调队列 = Promise 的骨架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 (e) { reject(e); }
  }
}

pending 时回调被囤积在数组里;状态一旦落定,批量释放回调。这就是 Promise 处理异步的秘诀。

2. then 返回新 Promise —— 链式调用的灵魂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
then(onFulfilled, onRejected) {
  // 值穿透
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };

  const promise2 = new MyPromise((resolve, reject) => {
    const handle = (fn, val) => {
      queueMicrotask(() => {
        try {
          const x = fn(val);
          this.resolvePromise(promise2, x, resolve, reject);
        } catch (e) { reject(e); }
      });
    };

    if (this.state === FULFILLED) handle(onFulfilled, this.value);
    else if (this.state === REJECTED) handle(onRejected, this.reason);
    else {
      this.onFulfilledCallbacks.push(() => handle(onFulfilled, this.value));
      this.onRejectedCallbacks.push(() => handle(onRejected, this.reason));
    }
  });

  return promise2;
}

关键细节:

  • 返回新 Promisethen(() => 1).then(() => 2) 能链起来,是因为每个 then 都返回独立的 Promise
  • 值穿透.then() 不传回调时,值原样传递;.then(null, null) 不会断链
  • 微任务:回调必须用 queueMicrotask 异步执行,符合 Promise/A+

3. resolvePromise —— 处理 then 回调的返回值

这是手写 Promise 最复杂的部分,规范里叫 Promise Resolution Procedure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
resolvePromise(promise2, x, resolve, reject) {
  // 防止循环引用
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected'));
  }

  // x 是 MyPromise 实例 → 等它完成
  if (x instanceof MyPromise) {
    return x.then(resolve, reject);
  }

  // x 是 thenable 对象(有 then 方法)
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let called = false;
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(x,
          v => { if (!called) { called = true; resolve(v); } },
          e => { if (!called) { called = true; reject(e); } }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (!called) reject(e);
    }
    return;
  }

  // 普通值
  resolve(x);
}

三段分支:Promise → 等;thenable → 调 then 并防多次调用;普通值 → 直接 resolve。

4. catch / finally

1
2
3
4
5
6
7
8
9
10
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; })
  );
}

catchthen(null, fn) 的语法糖。finally 关键是透传:原来的 value/reason 不变,只额外执行 callback。

5. 静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static resolve(value) {
  if (value instanceof MyPromise) return value;
  return new MyPromise(r => r(value));
}

static reject(reason) {
  return new MyPromise((_, r) => r(reason));
}

static all(promises) {
  return new MyPromise((resolve, reject) => {
    const results = [];
    let count = 0;
    if (promises.length === 0) return resolve([]);
    promises.forEach((p, i) => {
      MyPromise.resolve(p).then(v => {
        results[i] = v; // 保持顺序
        if (++count === promises.length) resolve(results);
      }, reject); // 任一失败立即 reject
    });
  });
}

static race(promises) {
  return new MyPromise((resolve, reject) => {
    promises.forEach(p => MyPromise.resolve(p).then(resolve, reject));
  });
}

static allSettled(promises) {
  return new MyPromise((resolve) => {
    const results = [];
    let count = 0;
    if (promises.length === 0) return resolve([]);
    promises.forEach((p, i) => {
      MyPromise.resolve(p).then(
        v  => { results[i] = { status: 'fulfilled', value: v }; if (++count === promises.length) resolve(results); },
        e  => { results[i] = { status: 'rejected', reason: e }; if (++count === promises.length) resolve(results); }
      );
    });
  });
}

static any(promises) {
  return new MyPromise((resolve, reject) => {
    const errors = [];
    let count = 0;
    if (promises.length === 0) return reject(new AggregateError([], 'All promises were rejected'));
    promises.forEach((p, i) => {
      MyPromise.resolve(p).then(resolve, e => {
        errors[i] = e;
        if (++count === promises.length) reject(new AggregateError(errors, 'All promises were rejected'));
      });
    });
  });
}

「其实你每天都在用」

1. Node.js 的 fs.promises

1
2
const fs = require('fs/promises');
await fs.readFile('a.txt'); // 内部返回 Promise

2. axios / fetch 返回 Promise

1
axios.get('/api').then(r => r.data); // axios 内部 new Promise(...)

3. React.lazy

1
2
const Lazy = React.lazy(() => import('./Heavy'));
// import() 返回 Promise,lazy 内部等它 resolve

4. Web API:navigator.clipboard

1
navigator.clipboard.writeText('hello').then(() => console.log('已复制'));

5. 所有 async 函数

每个 async function 的返回值都经过我们手写的这一套逻辑。


常见误解(FAQ)

❌ 误区 1:「then 返回 this 就行」

不行。如果返回 this,链上的状态就共享了——p.then(() => 'a').then(() => 'b') 第二个 then 收到的不是 ‘a’,而是原始 Promise 的值。这就是为什么必须返回新 Promise。

❌ 误区 2:「resolvePromise 里判断 thenable 只是锦上添花」

这是规范要求的核心逻辑,不是可选的。如果你的 Promise resolve 了一个 jQuery 的 Deferred、或者另一个 Promise 库的实例,必须能正确处理。否则跨库互操作直接崩。

❌ 误区 3:「用 setTimeout 模拟微任务也行」

会破坏执行顺序。Promise.then 如果走 setTimeout(宏任务),输出顺序就和原生 Promise 不一致。面试官一眼看穿。

❌ 误区 4:「手写 Promise 只需要实现 then」

catchfinallyallraceresolvereject 都是常见追问。Promise.allSettledPromise.any 是进阶加分项。


一句话总结

手写 Promise 真正在写的不是代码,是状态流转的纪律——何时等、何时调、何时传,每一步都在训练你对异步的掌控力。

本文由作者按照 CC BY 4.0 进行授权