- Published on
Promise 与 Event Loop
Promise
Promise 是 JavaScript 中处理异步操作的核心对象。它代表一个尚未完成但最终会完成(或失败)的操作及其结果值。可以将它理解为一个“承诺”或一个未来值的“占位符”。
以 fetch
为例,当我们调用 fetch('/api')
时,会执行两件事:
- 浏览器层面:浏览器的网络模块(通常底层是通过 XMLHttpRequest (XHR) 或更新的 Fetch API 实现)接收指令,在后台发起一个真实的 HTTP 网络请求。
- JS 层面:
fetch
函数会立即、同步地返回一个 Promise 对象。这个对象此时的状态是pending
(进行中),它承诺在未来网络请求有结果时通知我们。
XMLHttpRequest (XHR)
是较早的浏览器 API,支持几乎所有浏览器版本,底层稳定可靠。Fetch API
是现代浏览器提供的更简洁 Promise 风格封装,语法更直观,但在较旧的浏览器(如 IE)中不支持,通常需使用 polyfill。- 实际中,许多浏览器内部实现的
fetch
仍可能基于XHR
,但它们的接口和使用体验是完全不同的。
Promise 的内部结构
一个 Promise 对象在内部维护着它的状态和结果,可以通过一个心智模型来理解其核心构成:
[[PromiseState]]
: 内部状态,状态一旦改变,就不能再变。有三种可能:pending
:进行中,初始状态。fulfilled
:已成功,操作成功完成。rejected
:已失败,操作失败。
[[PromiseResult]]
: 内部结果,存储着操作成功后的值 (value) 或失败后的原因 (reason)。[[PromiseFulfillReactions]]
/[[PromiseRejectReactions]]
: 两个内部列表,用于存放通过.then()
或.catch()
注册的、将在 Promise 状态变为fulfilled
或rejected
后执行的回调函数。
我们通过 .then()
和 .catch()
方法来“订阅” Promise 的最终结果。
.then(onFulfilled, onRejected)
: 分别注册成功和失败的回调。.catch(onRejected)
: 只是.then(null, onRejected)
的语法糖,专门用于注册失败回调。
new Promise
的执行器是同步的传递给 new Promise
的执行器函数 (resolve, reject) => { ... }
会在 Promise 实例被创建时立即、同步地执行,并不会被放入任何异步队列。
console.log('1. Script start');
new Promise((resolve, reject) => {
console.log('2. Promise executor executed synchronously');
resolve();
});
console.log('3. Script end');
// 输出顺序: 1, 2, 3
链式调用
每当调用 .then
或 .catch
时,它都会返回一个全新的 Promise 对象。这个特性是实现链式调用的基础,它允许我们将多个异步操作优雅地串联起来。
const promise1 = fetch('/api/data');
// promise2 是 .then() 返回的一个全新的 Promise
const promise2 = promise1.then(data => {
console.log(data);
return 'some new value'; // 这个返回值会成为 promise2 的成功结果
});
// promise3 是第二个 .then() 返回的又一个全新的 Promise
const promise3 = promise2.then(newValue => {
console.log(newValue); // 输出 'some new value'
});
静态组合方法
Promise
对象提供了一系列强大的静态方法,用于处理多个 Promise 的组合情况,以满足复杂的并发或竞争需求。
Promise.all()
Promise.all(iterable)
方法用于处理多个 Promise 的并行执行,并等待所有 Promise 都成功。
- 成功条件: 传入的所有 Promise 都变为
fulfilled
。最终Promise.all
返回的 Promise 会fulfilled
,其值为一个包含了所有输入 Promise 结果的数组(顺序与输入顺序一致)。 - 失败条件: 只要有一个 Promise 变为
rejected
,Promise.all
会立即rejected
,其原因就是第一个失败的 Promise 的原因。所有其他 Promise 的结果都会被忽略。
Promise.allSettled()
Promise.allSettled(iterable)
同样并行执行多个 Promise,但它会等待所有 Promise 都“尘埃落定”(settled
),无论其最终状态是成功还是失败。
- 行为: 它永远不会
rejected
。它总是在所有输入的 Promise 都完成后fulfilled
。 - 返回值: 其成功的值是一个对象数组,每个对象都描述了对应 Promise 的最终结果,格式为:
- 成功:
{ status: 'fulfilled', value: result }
- 失败:
{ status: 'rejected', reason: error }
- 成功:
Promise.race()
Promise.race(iterable)
方法会返回一个 Promise,这个 Promise 的状态会与第一个“尘埃落定”的 Promise 的状态保持一致。
- 行为: 就像一场赛跑,谁第一个到达终点(无论是成功
fulfilled
还是失败rejected
),Promise.race
就采纳谁的结果,并忽略所有其他“跑得慢”的 Promise。 - 平局规则: 如果多个 Promise 在同一时间点
settle
(在代码层面,通常指同步的 Promise),则会选择在传入的迭代器中排序靠前的那个。
Promise.any()
Promise.any(iterable)
类似于 race
,但它只关心“成功”。
- 成功条件: 等待第一个
fulfilled
的 Promise。一旦有一个 Promise 成功,Promise.any
就会立即fulfilled
,其值就是那个成功 Promise 的值。 - 失败条件: 只有当所有传入的 Promise 都
rejected
时,Promise.any
才会rejected
。 - 特殊的失败原因: 其失败的原因不是一个普通的错误,而是一个
AggregateError
对象,这个对象的errors
属性是一个数组,包含了所有 Promise 的失败原因。
Event Loop:异步的调度中心
当一个 Promise 准备就绪(例如,网络请求已返回),它的回调函数并不会立即执行。相反,它会被放入一个特定的队列,等待 Event Loop 的调度。
JavaScript 的异步机制由宏任务 (Macrotask) 和 微任务 (Microtask) 共同驱动:
- 宏任务 (Macrotask): 包括
script
(整个脚本)、setTimeout
、setInterval
、I/O 操作、UI 事件等。 - 微任务 (Microtask): 包括
Promise.prototype.then/catch/finally
、queueMicrotask
等。
在任意一个事件循环周期 (tick) 中,引擎会先从宏任务队列中取一个任务执行 (第一个是 script
)。在该宏任务执行完毕后,引擎会立即检查并清空整个微任务队列,然后才可能进行UI渲染,并接着开始下一个宏任务。