文章

Promise核心原理深度解析

深入剖析 Promise 状态机机制、then 链式调用的实现原理,理解 resolve/reject 的处理规则以及 Promise 错误冒泡机制。

Promise核心原理深度解析

一句话概括

Promise 是 JavaScript 异步编程的基石,状态机+回调队列的设计使其能够优雅地处理异步流程,then 的链式调用则实现了从”回调地狱”到”链式编程”的跨越。


背景

在 Promise 出现之前,JavaScript 处理异步主要依靠回调函数。回调嵌套过深导致”回调地狱”,代码可读性和可维护性极差。Promise 最早由社区提出(Defer/A 对象),后来被 ES6 标准化,引入了 Promise/A+ 规范,提供了一种更优雅的异步编程模型。


概念与定义

Promise 的三种状态

状态说明能否转换
pending初始状态,可以转向 fulfilledrejected
fulfilled操作成功,值不可改变
rejected操作失败,错误不可改变

Promise 状态的转换是单向的、不可逆的,且一旦状态确定就不能再改变。

Promise/A+ 规范核心

  • Promise 状态只能从 pending 转换到 fulfilledrejected
  • 必须有 then 方法,接受 onFulfilledonRejected 两个回调
  • then 必须返回一个新的 Promise(链式调用的基础)
  • 回调必须异步执行(microtask 队列)

最小示例

1
2
3
4
5
6
7
8
9
10
11
12
13
// 基本用法
new Promise((resolve, reject) => {
  setTimeout(() => resolve('data'), 1000);
})
  .then(res => console.log(res))      // 1秒后输出: data
  .catch(err => console.error(err));  // 捕获前一个 Promise 的错误

// Promise 链
Promise.resolve(1)
  .then(x => x + 1)      // 2
  .then(x => Promise.resolve(x + 1)) // 3 (Promise 会展开)
  .then(x => x + 1)     // 4
  .then(console.log);   // 4

核心知识点拆解

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
30
31
32
33
34
35
36
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) {
        this.state = FULFILLED;
        this.value = value;
        // 执行所有等待中的回调
        this.onFulfilledCallbacks.forEach(fn => fn(value));
      }
    };

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

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

2. resolve/reject 的处理规则

Promise 的 resolvereject 需要处理三种情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const resolve = (value) => {
  if (value instanceof MyPromise) {
    // 如果返回值是 Promise,直接等待其结果
    return value.then(resolve, reject);
  }
  // 基本类型或普通对象,直接 fulfilled
  this.state = FULFILLED;
  this.value = value;
};

// reject 无需特殊处理,只需要 throw 一个错误
const reject = (reason) => {
  this.state = REJECTED;
  this.reason = reason;
};

为什么 resolve 要处理 Promise 展开? 因为 Promise 链式调用的核心是”then 返回的 Promise 等待前一个 Promise 完成”。如果 resolve 了一个 Promise,需要等待它完成后再继续。

3. then 链式调用的实现

then 的返回值不是当前 Promise 本身,而是一个新的 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
then(onFulfilled, onRejected) {
  // 返回新 Promise,用于链式
  return new MyPromise((resolve, reject) => {
    const handleCallback = (callback, fallback) => {
      try {
        const result = callback
          ? onFulfilled(this.value)
          : onRejected(this.reason);
        resolve(result); // 新 Promise 的 resolve 决定链的后续状态
      } catch (e) {
        reject(e); // 抛出异常 → 新 Promise 变为 rejected
      }
    };

    if (this.state === FULFILLED) {
      // 已完成 → 异步执行回调(使用微任务)
      queueMicrotask(() => handleCallback(onFulfilled));
    } else if (this.state === REJECTED) {
      queueMicrotask(() => handleCallback(onRejected));
    } else {
      // pending → 缓存回调,等状态转换时执行
      this.onFulfilledCallbacks.push(() => handleCallback(onFulfilled));
      this.onRejectedCallbacks.push(() => handleCallback(onRejected));
    }
  });
}

4. 错误冒泡机制

在 then 链中,如果某个 Promise 的 onFulfilled 抛出异常,后续 Promise 会自动变为 rejected:

1
2
3
4
5
Promise.resolve(1)
  .then(x => { throw new Error('oops'); }) // rejected
  .then(x => x + 1)                        // 被跳过
  .catch(err => console.log(err.message)); // 输出: oops
  .finally(() => console.log('done'));     // 输出: done

这是因为 then(onFulfilled) 内部实际上相当于 then(onFulfilled, undefined),而 onRejected 负责将异常传递给下一个 Promise。

5. Promise.then 的异步执行

Promise 回调必须异步执行,这是 Promise/A+ 规范的要求:

1
2
3
4
5
6
// 使用 queueMicrotask(相当于 Promise.resolve().then)
queueMicrotask(() => {
  if (this.state === FULFILLED) {
    handleCallback(onFulfilled);
  }
});

实战案例

案例一:实现红绿灯交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 经典 promise 面试题:红灯3秒后变绿,绿灯1秒后变黄,黄灯2秒后变红,循环
function red() { console.log('red'); }
function green() { console.log('green'); }
function yellow() { console.log('yellow'); }

function trafficLight() {
  const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
  
  async function run() {
    while (true) {
      red();
      await sleep(3000);
      green();
      await sleep(1000);
      yellow();
      await sleep(2000);
    }
  }
  run();
}

trafficLight();

案例二:Promise 粉尘化问题(microtask vs macrotask)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
console.log('script start');

setTimeout(() => console.log('setTimeout'), 0); // macrotask

Promise.resolve()
  .then(() => console.log('Promise1'))
  .then(() => console.log('Promise2'));

Promise.resolve()
  .then(() => console.log('Promise3'));

console.log('script end');

// 输出顺序:
// script start
// script end
// Promise1
// Promise3
// Promise2   ← 微任务链式调用是顺序的
// setTimeout ← 微任务全部完成后才执行下一个宏任务

案例三:封装请求方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function request(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.responseText);
    xhr.onerror = () => reject(new Error('Network Error'));
    xhr.send();
  });
}

// 使用
request('/api/user')
  .then(user => request(`/api/profile/${user.id}`))
  .then(profile => console.log(profile))
  .catch(err => console.error(err));

底层原理

Promise 执行时序

JavaScript 引擎中存在两种任务队列:

1
2
3
4
宏任务队列(macrotask): setTimeout, setInterval, I/O, UI渲染
微任务队列(microtask): Promise.then, MutationObserver, queueMicrotask

执行顺序:执行当前宏任务 → 清空微任务队列 → 取下一个宏任务

Promise 的回调(then/catch/finally)始终进入微任务队列,这就是为什么 Promise 比 setTimeout 更快执行。

Promise 的状态转换图

1
2
3
4
5
6
7
8
          executor 执行
              ↓
           pending
         ↙          ↘
    resolve()      reject()
         ↓          ↓
    fulfilled      rejected
      (不可逆)      (不可逆)

高频面试题解析

Q1:Promise 的三种状态是什么?状态能否逆转?

:三种状态:pending(初始)、fulfilled(成功)、rejected(失败)。状态一旦确定就不能再改变,这是 Promise 不可变性的体现。

Q2:resolve 和 reject 的区别是什么?

resolve 将 Promise 变为 fulfilled 状态,携带成功值;reject 将 Promise 变为 rejected 状态,携带失败原因。注意 resolve 如果接收到另一个 Promise,会等待该 Promise 完成(展开)。

Q3:then 的链式调用是如何实现的?

:每个 then 方法返回一个新的 Promise。这个新 Promise 的状态由 onFulfilled/onRejected 的返回值决定:

  • 正常返回值 → 新 Promise fulfilled
  • 抛出异常 → 新 Promise rejected
  • 返回 Promise → 等待其完成后,新 Promise 才确定状态

Q4:Promise.then 是同步还是异步?

:Promise.then 的回调是异步执行的(进入微任务队列)。这是 Promise/A+ 规范的要求,确保 Promise 链总是异步执行,避免同步回调导致的竞态问题。

Q5:catch 可以捕获前几个 Promise 的错误?

:catch 会捕获之前所有未处理的 Promise 错误,直到遇到 catch 为止。catch 之后如果没有新的错误,后续 then 会正常执行。简言之:catch 之后,链重新变为 fulfilled 状态

Q6:finally 与 catch 的区别?

:finally 不管 Promise 是 fulfilled 还是 rejected 都会执行,且不改变 Promise 的状态(会传递前一个 Promise 的结果或错误)。主要用于清理操作,如关闭 loading。


完整 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
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
57
58
59
60
61
62
63
64
65
66
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 (value instanceof MyPromise) {
        return value.then(resolve, reject);
      }
      if (this.state === PENDING) {
        this.state = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => queueMicrotask(() => fn(value)));
      }
    };

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

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

  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, fallback) => {
        try {
          queueMicrotask(() => {
            try {
              const result = fn(this.state === FULFILLED ? this.value : this.reason);
              resolve(result);
            } catch (e) { reject(e); }
          });
        } catch (e) { reject(e); }
      };

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

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

  static resolve(v) { return new MyPromise(r => r(v)); }
  static reject(e) { return new MyPromise((_, r) => r(e)); }
}

总结与扩展

要点说明
状态pending → fulfilled/rejected(不可逆)
resolve 展开resolve(Promise) 会等待内部 Promise 完成
then 返回返回新 Promise,状态由回调返回值决定
错误冒泡未捕获的异常会自动冒泡到下一个 catch
异步执行then 回调进入微任务队列,比 setTimeout 更早

扩展阅读

  • Promise/A+ 规范原文:promisesaplus.com
  • queueMicrotaskPromise.resolve().then 的关系
  • 异步迭代器 Symbol.asyncIteratorfor await...of
本文由作者按照 CC BY 4.0 进行授权