Promise核心原理深度解析
深入剖析 Promise 状态机机制、then 链式调用的实现原理,理解 resolve/reject 的处理规则以及 Promise 错误冒泡机制。
一句话概括
Promise 是 JavaScript 异步编程的基石,状态机+回调队列的设计使其能够优雅地处理异步流程,then 的链式调用则实现了从”回调地狱”到”链式编程”的跨越。
背景
在 Promise 出现之前,JavaScript 处理异步主要依靠回调函数。回调嵌套过深导致”回调地狱”,代码可读性和可维护性极差。Promise 最早由社区提出(Defer/A 对象),后来被 ES6 标准化,引入了 Promise/A+ 规范,提供了一种更优雅的异步编程模型。
概念与定义
Promise 的三种状态
| 状态 | 说明 | 能否转换 |
|---|---|---|
pending | 初始状态,可以转向 fulfilled 或 rejected | ✅ |
fulfilled | 操作成功,值不可改变 | ❌ |
rejected | 操作失败,错误不可改变 | ❌ |
Promise 状态的转换是单向的、不可逆的,且一旦状态确定就不能再改变。
Promise/A+ 规范核心
- Promise 状态只能从
pending转换到fulfilled或rejected - 必须有
then方法,接受onFulfilled和onRejected两个回调 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 的 resolve 和 reject 需要处理三种情况:
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
queueMicrotask与Promise.resolve().then的关系- 异步迭代器
Symbol.asyncIterator与for await...of