文章

Promise核心原理深度解析

拆解 Promise 状态机机制、then 链式调用、错误冒泡与微任务调度,掌握异步编程的核心范式。

Promise核心原理深度解析

一句话概括

Promise 本质上是一个带有回调队列的状态机——pending 时囤积回调,fulfilled/rejected 后逐一唤醒;每个 then 又生出一个新 Promise,链式调用的根基全在于此。


核心知识点

1. 状态机:一次变心,终身不悔

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

三个关键:状态不可逆、回调队列、executor 异常自动 reject。

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

  return new MyPromise((resolve, reject) => {
    const handle = (fn, value) => {
      queueMicrotask(() => {
        try {
          const x = fn(value);
          // 如果回调返回 Promise,等待它落定
          if (x instanceof MyPromise) x.then(resolve, reject);
          else resolve(x);
        } 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));
    }
  });
}

链式调用的灵魂:then 不返回 this,而是返回全新的 Promise。新 Promise 的状态由回调的返回值决定。

3. 错误冒泡 —— 没 catch 就一直漏下去

1
2
3
4
5
6
7
8
9
Promise.resolve()
  .then(() => { throw new Error('boom'); }) // rejected
  .then(() => console.log('skip'))          // 被跳过
  .catch(e => console.log(e.message));      // 接住了 boom

// catch 之后链恢复
Promise.reject('err')
  .catch(() => 'recovered')
  .then(v => console.log(v)); // 'recovered'

原理:then(onFulfilled) 等价于 then(onFulfilled, undefined)undefined 被替换成默认的 e => { throw e }——把错误往下传。

4. 微任务调度 —— queueMicrotask

1
2
3
4
console.log('start');
Promise.resolve().then(() => console.log('promise'));
console.log('end');
// 输出:start → end → promise

Promise 回调必须异步执行,且进微任务队列。这意味着它会抢在 setTimeout 前面,但排在同步代码后面。

5. resolve(Promise) 的特殊处理

1
2
const inner = new Promise(r => setTimeout(() => r('done'), 100));
new Promise(resolve => resolve(inner)).then(console.log); // 100ms 后输出 done

如果 resolve 收到另一个 Promise,会”等待”它完成——规范叫 Promise Resolution Procedure,目的是让嵌套 Promise 自动展平。


「其实你每天都在用」

1. fetch API 的返回值

1
fetch('/api/user').then(r => r.json()); // fetch 返回 Promise

2. 图片预加载

1
2
3
4
5
6
const loadImg = src => new Promise((resolve, reject) => {
  const img = new Image();
  img.onload = () => resolve(img);
  img.onerror = reject;
  img.src = src;
});

3. setTimeout 的 Promise 化

1
2
const sleep = ms => new Promise(r => setTimeout(r, ms));
await sleep(1000); // 等 1 秒

4. async/await 底层

每个 await 等价于一个 .then()try/catch 等价于 .catch()。你写的 async 函数本质上是 Promise 链。

5. 上传进度监听

1
2
3
4
5
6
7
new Promise((resolve) => {
  const xhr = new XMLHttpRequest();
  xhr.upload.addEventListener('progress', e => updateBar(e.loaded / e.total));
  xhr.onload = resolve;
  xhr.open('POST', '/upload');
  xhr.send(formData);
});

常见误解(FAQ)

❌ 误区 1:「Promise 构造函数是异步的」

Promise 的 executornew Promise(fn) 里的 fn)是同步执行的。只有 .then() / .catch() / .finally() 里的回调是异步的。这是事件循环题的常见陷阱。

❌ 误区 2:「catch 之后不能再 then」

可以。catch 返回的也是一个 Promise,如果 catch 里没抛新错误,状态就是 fulfilled,后面的 then 正常执行。

❌ 误区 3:「finally 不改变链的值」

finally 的回调不接收值,也不改变 Promise 的最终结果——它会”透传”上一个 Promise 的值或错误。唯一的例外:如果 finally 回调本身抛异常或返回 rejected Promise,新错误会覆盖旧值。

❌ 误区 4:「resolve 一个 rejected Promise,当前 Promise 也 rejected」

对。resolve(Promise.reject('err')) 的结果是 rejected。因为 resolve 检测到参数是 Promise 后会”订阅”它的结果,而不是盲目地 fulfilled。


一句话总结

Promise 把”回调”变成了”值”——你可以把异步结果像普通变量一样传来传去、组合拼装,这彻底改变了 JavaScript 的编程模型。

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