开篇:异步编程的灯塔——Promises/A+ 规范
在现代JavaScript应用开发中,异步操作无处不在:网络请求、文件读写、定时器等等。然而,早期的异步编程模型,如回调函数(callback),常常导致“回调地狱”(Callback Hell),使代码难以阅读、维护和调试。为了解决这一痛点,Promise应运而生,它以一种更优雅、更结构化的方式处理异步操作。
Promise不仅仅是一种编程模式,更是一套被广泛接受的规范——Promises/A+ 规范。这个规范定义了Promise对象的行为、状态转换以及如何与.then方法交互,确保了不同Promise实现之间的互操作性。这意味着,无论是浏览器原生的Promise,还是各种第三方库实现的Promise,只要它们遵循Promises/A+规范,就能够无缝地协同工作。
作为一名编程专家,深入理解并手写实现一个满足Promises/A+规范的Promise,不仅能帮助我们彻底掌握异步编程的核心机制,还能提升我们对JavaScript运行时、事件循环和微任务队列的理解。本次讲座,我们将从Promise最基础的状态机开始,逐步构建起一个完整的Promise实现,直至其最精妙的链式调用机制。
第一章:Promise 的骨架——状态机
Promise的核心在于其严谨的状态机模型。一个Promise对象在其生命周期中,只会处于以下三种状态之一:
- Pending (待定):初始状态,既不是成功也不是失败。
- Fulfilled (已成功):操作成功完成。
- Rejected (已失败):操作失败。
这些状态之间的转换是单向且不可逆的。一旦Promise从Pending状态转换为Fulfilled或Rejected,它就进入了Settled (已落定) 状态,其状态将永远保持不变,并且绑定的值或拒绝原因也不会再改变。
状态转换规则:
- Pending 状态可以转换为 Fulfilled 状态,伴随一个值 (value)。
- Pending 状态可以转换为 Rejected 状态,伴随一个原因 (reason)。
- Fulfilled 状态不能转换为任何其他状态。
- Rejected 状态不能转换为任何其他状态。
我们可以用一个表格来清晰地表示这些状态和转换:
| 状态 | 描述 | 转换来源 | 转换去向 | 关联数据 |
|---|---|---|---|---|
pending |
初始状态 | (无) | fulfilled / rejected |
(无) |
fulfilled |
操作成功 | pending |
(无) | 成功值 |
rejected |
操作失败 | pending |
(无) | 失败原因 |
为了在代码中实现这个状态机,我们需要在MyPromise类的实例中存储当前状态、成功值或失败原因,以及一个用于存储待执行回调的队列。
// 定义Promise的三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 用于调度微任务的辅助函数
// 在Node.js环境下使用process.nextTick,浏览器环境下优先使用MutationObserver,
// 最后降级为setTimeout(0)。这确保了回调在当前宏任务结束后、下一个宏任务开始前执行。
const _nextTick = (() => {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
return process.nextTick;
} else if (typeof MutationObserver === 'function') {
let callbacks = [];
const observer = new MutationObserver(() => {
const temp = callbacks;
callbacks = [];
temp.forEach(cb => cb());
});
const textNode = document.createTextNode('');
observer.observe(textNode, { characterData: true });
let toggle = false;
return (cb) => {
callbacks.push(cb);
toggle = !toggle;
textNode.data = toggle ? '1' : '0'; // 触发MutationObserver
};
} else {
return (cb) => setTimeout(cb, 0);
}
})();
class MyPromise {
constructor(executor) {
// 1. 初始化状态
this._state = PENDING;
this._value = undefined; // 存储成功的值
this._reason = undefined; // 存储失败的原因
// 2. 存储待执行的成功/失败回调函数
// Promises/A+ 规范要求 onFulfilled/onRejected 必须异步执行,且在一个 Promise 被决议后,
// 任何后续对 .then 的调用,其相应的回调函数都必须被异步执行。
// 所以我们使用队列来存储这些回调,在状态改变时异步触发。
this._fulfilledCallbacks = [];
this._rejectedCallbacks = [];
// 3. 构造函数内部,定义 resolve 和 reject 函数
// 并绑定 this,确保它们在任何地方被调用时都能正确地操作当前Promise实例
const resolve = (value) => {
// 使用 _nextTick 确保状态变更和回调执行是异步的
_nextTick(() => {
// 只有在 pending 状态时才能进行状态转换
if (this._state === PENDING) {
this._state = FULFILLED;
this._value = value;
// 遍历执行所有注册的成功回调
this._fulfilledCallbacks.forEach(callback => callback());
}
});
};
const reject = (reason) => {
_nextTick(() => {
if (this._state === PENDING) {
this._state = REJECTED;
this._reason = reason;
// 遍历执行所有注册的失败回调
this._rejectedCallbacks.forEach(callback => callback());
}
});
};
// 4. 执行 executor 函数
// executor 可能会同步抛出错误,需要捕获并调用 reject
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
// ... 后续将实现 .then 方法
}
这段代码构建了MyPromise的基础骨架。_state、_value、_reason是Promise的内部状态,_fulfilledCallbacks和_rejectedCallbacks是两个队列,用于存储通过.then方法注册的回调函数。resolve和reject函数负责改变Promise的状态,并异步触发相应的回调。executor函数是Promise的执行器,它会被立即同步执行,并接收resolve和reject作为参数。任何在executor中抛出的异常都应该导致Promise被拒绝。
第二章:Promise 的生命周期——构造函数与核心解析逻辑
在上一章的基础上,我们已经看到了MyPromise构造函数的初步实现。它接收一个executor函数,这个函数会被立即执行,并传入两个参数:resolve和reject。executor的职责是启动异步操作,并在操作成功时调用resolve,失败时调用reject。
resolve和reject的职责:
- 改变Promise状态:将
_state从PENDING更改为FULFILLED或REJECTED。 - 存储结果:将成功值存储到
_value,或将失败原因存储到_reason。 - 触发回调:遍历并执行
_fulfilledCallbacks或_rejectedCallbacks队列中的所有回调函数。 - 单次决议原则:一个Promise只能被决议(resolve或reject)一次。一旦状态从
PENDING变为FULFILLED或REJECTED,后续对resolve或reject的调用都将被忽略。 - 异步执行回调:Promises/A+ 规范明确要求
onFulfilled和onRejected回调必须作为微任务异步执行。这是为了确保一致的行为,避免同步执行可能导致的竞态条件或意外的副作用。我们通过_nextTick辅助函数来调度这些微任务。
让我们仔细审视resolve和reject函数的实现,它们是Promise生命周期的关键:
// ... (MyPromise class definition starts)
class MyPromise {
// ... (constructor and _nextTick defined as before)
constructor(executor) {
this._state = PENDING;
this._value = undefined;
this._reason = undefined;
this._fulfilledCallbacks = [];
this._rejectedCallbacks = [];
// 内部的 resolve 方法,用于将 Promise 状态从 pending 变为 fulfilled
const _resolve = (value) => {
// 确保状态改变和回调执行是异步的 (Promises/A+ 2.2.4)
_nextTick(() => {
// 只能从 pending 状态转换为 fulfilled (Promises/A+ 2.1.1.1)
if (this._state === PENDING) {
this._state = FULFILLED;
this._value = value;
// 执行所有已注册的 onFulfilled 回调 (Promises/A+ 2.2.6)
this._fulfilledCallbacks.forEach(callback => callback());
}
});
};
// 内部的 reject 方法,用于将 Promise 状态从 pending 变为 rejected
const _reject = (reason) => {
// 确保状态改变和回调执行是异步的 (Promises/A+ 2.2.4)
_nextTick(() => {
// 只能从 pending 状态转换为 rejected (Promises/A+ 2.1.1.2)
if (this._state === PENDING) {
this._state = REJECTED;
this._reason = reason;
// 执行所有已注册的 onRejected 回调 (Promises/A+ 2.2.6)
this._rejectedCallbacks.forEach(callback => callback());
}
});
};
// 执行 executor 函数,捕获可能抛出的同步错误 (Promises/A+ 2.3.3.3.4)
try {
executor(_resolve, _reject);
} catch (e) {
_reject(e); // 如果 executor 抛出错误,则拒绝 Promise
}
}
// ... (will implement .then method next)
}
这里我们把resolve和reject函数命名为_resolve和_reject,以区分它们与Promises/A+规范中的Promise Resolution Procedure,并强调它们是Promise实例内部用来改变状态的核心逻辑。
关于_nextTick的重要性:
_nextTick函数的使用是严格遵循Promises/A+规范的关键一环。规范要求onFulfilled和onRejected回调必须在当前执行栈清空后,以异步的方式执行。这通常通过将它们放入JavaScript的微任务队列来实现。
process.nextTick(Node.js):在当前事件循环的下一个“tick”中执行回调,优先级高于setTimeout(0)。MutationObserver(浏览器):利用DOM变化作为触发器,创建微任务。比setTimeout(0)更接近微任务的行为。setTimeout(0)(降级方案):将回调放入宏任务队列,优先级最低。在没有微任务机制的环境下,这是最接近异步执行的方式。
通过_nextTick,我们确保了resolve和reject即使被同步调用,其效果(状态改变和回调执行)也是在后续的微任务中完成的,从而避免了“同步决议”可能导致的问题,并保证了then方法总是返回一个新的Promise,且其回调总是异步执行。
第三章:连接未来的桥梁——.then 方法的实现
.then方法是Promise链式调用的核心,也是与外界交互的主要接口。它允许我们注册在Promise成功或失败时要执行的回调函数。Promises/A+ 规范对.then方法的行为有着严格的定义。
.then方法的规范要求:
- 签名:
promise.then(onFulfilled, onRejected)onFulfilled:可选参数,类型必须是函数。如果不是函数,则必须被忽略。onRejected:可选参数,类型必须是函数。如果不是函数,则必须被忽略。
- 返回值:
.then方法必须返回一个新的Promise对象,这正是链式调用的基础。 - 回调的异步执行:
onFulfilled和onRejected必须作为微任务异步执行。 - 回调的执行次数:
onFulfilled或onRejected只能被调用一次。 - 值传递:
- 如果
promise被fulfilled,onFulfilled会被调用,其第一个参数是promise的成功值。 - 如果
promise被rejected,onRejected会被调用,其第一个参数是promise的拒绝原因。
- 如果
- 错误处理:如果
onFulfilled或onRejected回调自身抛出异常,那么.then返回的新Promise必须被拒绝,拒绝原因为该异常。 - 透传:
- 如果
onFulfilled不是函数,且promise被fulfilled,then返回的新Promise必须以promise的成功值fulfilled。 - 如果
onRejected不是函数,且promise被rejected,then返回的新Promise必须以promise的拒绝原因rejected。
- 如果
现在,让我们来实现MyPromise的.then方法:
class MyPromise {
// ... (constructor and other methods as defined before)
then(onFulfilled, onRejected) {
// 1. 规范 2.2.1: onFulfilled 和 onRejected 必须是函数。
// 如果不是函数,我们需要创建一个“透传”函数,确保值或错误能传递下去。
// 规范 2.2.7.3: 如果 onFulfilled 不是函数,promise2 必须使用 promise1 的值 fulfill。
// 规范 2.2.7.4: 如果 onRejected 不是函数,promise2 必须使用 promise1 的原因 reject。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
// 2. 规范 2.2.7: .then 必须返回一个 Promise。
const promise2 = new MyPromise((resolve, reject) => {
// 3. 根据当前 Promise (this) 的状态来处理回调
if (this._state === FULFILLED) {
// 3.1 如果当前 Promise 已经 fulfilled,异步执行 onFulfilled
_nextTick(() => {
try {
// 3.1.1 规范 2.2.5, 2.2.7.1: onFulfilled 必须被调用,且以 value 为参数。
// 3.1.2 规范 2.2.7.1: onFulfilled 的返回值 x 决定 promise2 的状态。
const x = onFulfilled(this._value);
// 调用 Promise Resolution Procedure 来处理 x
_resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// 3.1.3 规范 2.2.7.2: 如果 onFulfilled 抛出异常 e,promise2 必须以 e 为原因 rejected。
reject(e);
}
});
} else if (this._state === REJECTED) {
// 3.2 如果当前 Promise 已经 rejected,异步执行 onRejected
_nextTick(() => {
try {
// 3.2.1 规范 2.2.5, 2.2.7.1: onRejected 必须被调用,且以 reason 为参数。
// 3.2.2 规范 2.2.7.1: onRejected 的返回值 x 决定 promise2 的状态。
const x = onRejected(this._reason);
// 调用 Promise Resolution Procedure 来处理 x
_resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// 3.2.3 规范 2.2.7.2: 如果 onRejected 抛出异常 e,promise2 必须以 e 为原因 rejected。
reject(e);
}
});
} else if (this._state === PENDING) {
// 3.3 如果当前 Promise 仍在 pending 状态,将回调函数保存起来
// 当 Promise 状态改变时,会遍历执行这些回调
this._fulfilledCallbacks.push(() => {
_nextTick(() => { // 再次确保异步
try {
const x = onFulfilled(this._value);
_resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
this._rejectedCallbacks.push(() => {
_nextTick(() => { // 再次确保异步
try {
const x = onRejected(this._reason);
_resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
});
return promise2; // 返回新的 Promise,实现链式调用
}
}
在then方法中,我们创建了一个新的MyPromise实例promise2并将其返回。这个promise2的状态将由onFulfilled或onRejected回调函数的执行结果来决定。
特别要注意的是,无论当前Promise处于何种状态,onFulfilled和onRejected回调的执行以及promise2的决议,都必须通过_resolvePromise函数来处理。这是Promises/A+规范中最复杂但也是最核心的部分——Promise决议过程。
第四章:链式调用的奥秘——Promise 决议过程 (Promise Resolution Procedure)
Promise Resolution Procedure,即Promise决议过程,是Promises/A+规范中最核心、最精妙的部分,也是实现.then链式调用和互操作性的关键。它的职责是根据onFulfilled或onRejected回调函数的返回值x,来决定由.then方法返回的新Promise (promise2) 的最终状态。
规范 2.3 详细描述了这个过程,我们将其封装在一个名为_resolvePromise(promise2, x, resolve, reject)的辅助函数中。
参数说明:
promise2:由.then方法返回的新Promise。x:onFulfilled或onRejected回调函数的返回值。resolve:promise2的resolve函数。reject:promise2的reject函数。
Promise决议过程 _resolvePromise 的详细逻辑:
| 规范点 | 条件 (x 的类型或特征) | 处理方式 My MyPromise {
constructor(executor) {
this._state = PENDING;
this._value = undefined;
this._reason = undefined;
this._fulfilledCallbacks = [];
this._rejectedCallbacks = [];
const _resolve = (value) => {
_nextTick(() => {
if (this._state === PENDING) {
this._state = FULFILLED;
this._value = value;
this._fulfilledCallbacks.forEach(callback => callback());
}
});
};
const _reject = (reason) => {
_nextTick(() => {
if (this._state === PENDING) {
this._state = REJECTED;
this._reason = reason;
this._rejectedCallbacks.forEach(callback => callback());
}
});
};
try {
executor(_resolve, _reject);
} catch (e) {
_reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
const handleCallback = (callback, valueOrReason) => {
_nextTick(() => {
try {
const x = callback(valueOrReason);
_resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
if (this._state === FULFILLED) {
handleCallback(onFulfilled, this._value);
} else if (this._state === REJECTED) {
handleCallback(onRejected, this._reason);
} else if (this._state === PENDING) {
this._fulfilledCallbacks.push(() => handleCallback(onFulfilled, this._value));
this._rejectedCallbacks.push(() => handleCallback(onRejected, this._reason));
}
});
return promise2;
}
}
/**
- Promise Resolution Procedure (规范 2.3)
- 用于处理 onFulfilled 或 onRejected 的返回值 x,以此决定 promise2 的状态。
- @param {MyPromise} promise2 – .then 方法返回的新 Promise
- @param {*} x – onFulfilled 或 onRejected 的返回值
- @param {Function} resolve – promise2 的 resolve 函数
-
@param {Function} reject – promise2 的 reject 函数
*/
function _resolvePromise(promise2, x, resolve, reject) {
// 2.3.1: 如果 promise2 和 x 指向同一个对象,则以 TypeError 拒绝 promise2。
// 这是为了防止循环引用,例如 return this;
if (promise2 === x) {
return reject(new TypeError(‘Chaining cycle detected for promise #’));
}// 2.3.2: 如果 x 是一个 Promise,则采用它的状态。
// 意思是如果 x 也是一个 Promise,promise2 将会等待 x 的状态。
if (x instanceof MyPromise) {
// 2.3.2.1: 如果 x 是 pending 状态,promise2 必须保持 pending 直到 x 变为 fulfilled 或 rejected。
// 2.3.2.2: 如果 x 是 fulfilled 状态,则以 x 的值 fulfill promise2。
// 2.3.2.3: 如果 x 是 rejected 状态,则以 x 的原因 reject promise2。
x.then(resolve, reject); // 这里直接将 promise2 的 resolve/reject 传递给 x.then
return;
}// 2.3.3: 如果 x 是一个对象或函数 (即可能是 thenable)。
// thenable 是指任何拥有 then 方法的对象或函数。
if (x && (typeof x === ‘object’ || typeof x === ‘function’)) {
let called = false; // 确保 thenable 的 then 方法只被调用一次try { // 2.3.3.1: 令 then 为 x.then。 // 2.3.3.2: 如果取 x.then 抛出异常 e,则以 e 拒绝 promise2。 const then = x.then; // 2.3.3.3: 如果 then 是一个函数,则以 x 作为 this 调用它, // 并传入两个回调函数:`resolvePromise` 和 `rejectPromise`。 if (typeof then === 'function') { then.call( x, // 2.3.3.3.1: 如果 `resolvePromise` 被调用,参数为 `y`,则运行 `[[Resolve]](promise2, y)`。 (y) => { if (called) return; // 确保只被调用一次 (Promises/A+ 2.3.3.3.3) called = true; _resolvePromise(promise2, y, resolve, reject); // 递归解析 y }, // 2.3.3.3.2: 如果 `rejectPromise` 被调用,参数为 `r`,则以 `r` 拒绝 promise2。 (r) => { if (called) return; // 确保只被调用一次 (Promises/A+ 2.3.3.3.3) called = true; reject(r); } ); return; // 处理完毕,返回 } } catch (e) { // 2.3.3.3.4: 如果调用 then 抛出异常 e, // 如果 `resolvePromise` 或 `rejectPromise` 已经被调用,则忽略该异常。 // 否则,以 e 拒绝 promise2。 if (called) return; // 如果已经调用过 resolvePromise/rejectPromise,则忽略后续的异常 reject(e); return; }}
// 2.3.4: 如果 x 既不是对象也不是函数,则以 x fulfill promise2。
resolve(x);
}
_resolvePromise 函数的核心逻辑分析:
-
循环引用检测 (2.3.1):如果
promise2和x是同一个对象,这意味着在onFulfilled或onRejected中返回了promise2自身,这会形成一个无限循环。规范要求此时必须以TypeError拒绝promise2。 -
x是一个 Promise (2.3.2):- 如果
x本身也是一个MyPromise实例,那么promise2的状态将完全跟随x的状态。 - 这通过
x.then(resolve, reject)实现:当x成功时,promise2也成功;当x失败时,promise2也失败。 - 这是实现Promise链式调用中,返回Promise的关键。
- 如果
-
x是一个对象或函数 (可能是 Thenable) (2.3.3):- 获取
then方法 (2.3.3.1 & 2.3.3.2):尝试获取x.then属性。如果获取过程中抛出异常,则promise2被拒绝。 then是函数 (2.3.3.3):如果x.then是一个函数,那么x被视为一个“thenable”对象。- 我们必须以
x作为this上下文调用x.then。 - 传入两个回调函数:
resolvePromise和rejectPromise。 resolvePromise(y):如果thenable内部调用了resolvePromise,且参数为y,那么我们需要递归地调用_resolvePromise(promise2, y, resolve, reject)来继续解析y。这允许thenable返回另一个thenable或Promise,实现更深层次的链式解析。rejectPromise(r):如果thenable内部调用了rejectPromise,且参数为r,那么promise2被以r拒绝。called标志 (2.3.3.3.3):thenable的then方法可能会多次调用resolvePromise或rejectPromise,或者同时调用两者。规范要求只接受第一次调用。called标志就是为此目的。- 异常处理 (2.3.3.3.4):如果在调用
then函数过程中或其内部抛出异常,如果此时resolvePromise或rejectPromise已经被调用过,则忽略该异常;否则,promise2被以该异常拒绝。
- 我们必须以
- 获取
-
x是普通值 (非对象、非函数) (2.3.4):- 如果
x不是对象也不是函数(即普通JavaScript值,如字符串、数字、布尔值、null、undefined),那么promise2直接以x为值被fulfilled。
- 如果
这个_resolvePromise函数是Promises/A+规范的精髓。它通过递归和状态管理,巧妙地将任意类型的返回值x,统一地映射到Promise的状态转换上,从而实现了Promise之间的无缝衔接和链式调用。
第五章:异步与微任务——确保正确时序
在Promise的实现中,异步执行是一个核心原则,而微任务队列则是实现这一原则的关键机制。
为什么需要异步执行?
Promises/A+规范 2.2.4 规定:onFulfilled或onRejected必须异步执行。具体来说,它们必须在then方法被调用的那个执行栈完成之后,且在新的宏任务开始之前执行。这背后的原因有:
- 避免竞态条件:如果回调同步执行,那么在某些情况下,Promise可能会在
executor函数中同步完成,导致then方法被调用时,Promise可能已经处于fulfilled或rejected状态。同步执行回调可能会在then方法返回新的Promise之前改变状态,从而引入难以预测的行为。异步执行确保了then方法总能返回一个pending状态的Promise(即使只是短暂的),再由微任务机制在稍后处理回调。 - 保证一致的行为:无论Promise是同步决议(例如
Promise.resolve(value))还是异步决议(例如网络请求),其.then回调的执行时机都应该保持一致。异步执行使得Promise的行为更加可预测。 - 避免栈溢出:在深层Promise链中,如果回调同步执行,可能会导致递归调用过深,从而引发栈溢出。将回调推入微任务队列,可以有效避免这个问题,因为每次回调都在一个新的(微)任务中执行,不会增加当前调用栈的深度。
微任务队列 (Microtask Queue) 与宏任务队列 (Macrotask Queue)
JavaScript运行时有一个事件循环机制,它管理着任务的执行。任务可以分为两类:
- 宏任务 (Macrotasks):如
setTimeout,setInterval,setImmediate(Node.js), I/O, UI渲染等。 - 微任务 (Microtasks):如
Promise的回调 (.then,.catch,.finally),MutationObserver的回调,process.nextTick(Node.js)等。
事件循环在一个宏任务执行完成后,会检查并清空所有可用的微任务,然后才进入下一个宏任务。这意味着微任务的优先级高于宏任务。
我们在代码中已经引入了_nextTick辅助函数,它就是用来将回调函数推入微任务队列的机制:
// ... (MyPromise class definition starts)
// 用于调度微任务的辅助函数
// 在Node.js环境下使用process.nextTick,浏览器环境下优先使用MutationObserver,
// 最后降级为setTimeout(0)。这确保了回调在当前宏任务结束后、下一个宏任务开始前执行。
const _nextTick = (() => {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
return process.nextTick; // Node.js 环境
} else if (typeof MutationObserver === 'function') {
let callbacks = [];
const observer = new MutationObserver(() => {
// 当 MutationObserver 被触发时,执行所有收集到的回调
const temp = callbacks;
callbacks = []; // 清空队列,准备下一轮
temp.forEach(cb => cb());
});
const textNode = document.createTextNode('');
observer.observe(textNode, { characterData: true }); // 观察一个文本节点的 characterData 变化
let toggle = false;
return (cb) => {
callbacks.push(cb); // 将回调函数添加到队列
toggle = !toggle;
textNode.data = toggle ? '1' : '0'; // 改变文本节点数据,触发 MutationObserver
};
} else {
return (cb) => setTimeout(cb, 0); // 浏览器降级方案,作为宏任务执行
}
})();
// ... (MyPromise class definition continues)
_nextTick在Promise实现中的应用:
-
_resolve和_reject方法内部:const _resolve = (value) => { _nextTick(() => { // 确保状态变更和回调执行是异步的 if (this._state === PENDING) { this._state = FULFILLED; this._value = value; this._fulfilledCallbacks.forEach(callback => callback()); } }); };这里
_nextTick包裹了状态修改和回调触发的逻辑。这意味着即使executor同步调用了_resolve,Promise的状态改变和_fulfilledCallbacks的执行也会被推迟到当前宏任务结束后。 -
then方法内部:- 当Promise处于
FULFILLED或REJECTED状态时,onFulfilled或onRejected回调的执行也被包裹在_nextTick中。if (this._state === FULFILLED) { handleCallback(onFulfilled, this._value); // handleCallback 内部也有 _nextTick } // ... const handleCallback = (callback, valueOrReason) => { _nextTick(() => { // 确保回调执行异步 try { const x = callback(valueOrReason); _resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }; - 当Promise处于
PENDING状态时,回调被注册到_fulfilledCallbacks或_rejectedCallbacks队列中。当Promise最终决议时,这些回调会被取出并执行,而执行它们的代码本身已经在_resolve和_reject中被_nextTick包裹,因此自然也是异步的。
- 当Promise处于
通过这种方式,我们严格遵循了Promises/A+规范关于异步执行的要求,确保了Promise行为的稳定性和一致性。理解并正确运用微任务队列,是掌握Promise高级特性的关键。
第六章:完善与优化——边缘情况处理
一个健壮的Promise实现需要考虑各种边缘情况和细节,以确保其完全符合Promises/A+规范。
1. onFulfilled/onRejected 参数的默认行为 (透传)
规范 2.2.7.3 和 2.2.7.4 规定:
- 如果
onFulfilled不是函数,且promise1被fulfilled,那么promise2(由promise1.then()返回的)必须以promise1的成功值fulfilled。 - 如果
onRejected不是函数,且promise1被rejected,那么promise2必须以promise1的拒绝原因rejected。
这意味着,如果我们在.then中省略了某个回调,它应该表现为“透传”:成功值继续传递给下一个onFulfilled,拒绝原因继续传递给下一个onRejected。
在我们的then方法中,已经处理了这一点:
then(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,创建一个默认函数,直接返回传入的值
// 使得 promise2 能够继续以 promise1 的值 fulfilled
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// 如果 onRejected 不是函数,创建一个默认函数,抛出传入的原因
// 使得 promise2 能够继续以 promise1 的原因 rejected (因为抛出的错误会被 catch 捕获并 reject promise2)
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
// ...
}
- 对于
onFulfilled的默认值value => value,当它被调用时,返回的就是原始的成功值。_resolvePromise会收到这个值,并用它来resolvepromise2。 - 对于
onRejected的默认值reason => { throw reason; },当它被调用时会抛出原始的拒绝原因。.then方法中的try...catch会捕获这个错误,并用它来rejectpromise2。这巧妙地实现了拒绝原因的透传。
2. Promise.resolve 和 Promise.reject 静态方法
这两个静态方法是创建已决议Promise的便捷方式。
Promise.resolve(value):返回一个以value成功决议的Promise。如果value本身是一个Promise,则返回该Promise。如果value是一个thenable,则会尝试解析thenable。Promise.reject(reason):返回一个以reason拒绝决议的Promise。
// 在 MyPromise 类外部或者作为静态方法实现
MyPromise.resolve = function(value) {
// 如果 value 是 MyPromise 实例,直接返回
if (value instanceof MyPromise) {
return value;
}
// 否则,返回一个新的 MyPromise,并立即 resolve
return new MyPromise(resolve => {
resolve(value); // 这里的 resolve 会触发 _resolvePromise 逻辑
});
};
MyPromise.reject = function(reason) {
// 返回一个新的 MyPromise,并立即 reject
return new MyPromise((_, reject) => {
reject(reason);
});
};
注意MyPromise.resolve的实现中,它会创建一个新的Promise,并调用resolve(value)。这个resolve是我们MyPromise构造函数中定义的内部_resolve函数。当value是thenable时,_resolve内部会调用_resolvePromise,从而正确处理thenable的解析。
3. 错误处理与冒泡
Promise的错误处理机制是一个非常强大的特性。当一个Promise被拒绝时,如果没有onRejected回调来处理它,错误会沿着Promise链向下冒泡,直到遇到一个onRejected回调或者到达链的末端。
executor中的错误:在MyPromise的构造函数中,我们使用try...catch来捕获executor函数中可能抛出的同步错误,并将其转换为Promise的拒绝原因。onFulfilled/onRejected回调中的错误:在.then方法中,onFulfilled和onRejected回调的执行也包裹在try...catch中。如果回调内部抛出异常,这个异常会成为promise2的拒绝原因。- 未捕获的错误:如果Promise链的最后没有
onRejected回调来处理错误,或者最后一个onRejected回调本身又抛出了错误,那么这个错误通常会成为“未捕获的Promise拒绝”,在浏览器中会触发unhandledrejection事件,在Node.js中会触发unhandledRejection进程事件。
一个完整的MyPromise类:
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const _nextTick = (() => {
if (typeof process === 'object' && typeof process.nextTick === 'function') {
return process.nextTick;
} else if (typeof MutationObserver === 'function') {
let callbacks = [];
const observer = new MutationObserver(() => {
const temp = callbacks;
callbacks = [];
temp.forEach(cb => cb());
});
const textNode = document.createTextNode('');
observer.observe(textNode, { characterData: true });
let toggle = false;
return (cb) => {
callbacks.push(cb);
toggle = !toggle;
textNode.data = toggle ? '1' : '0';
};
} else {
return (cb) => setTimeout(cb, 0);
}
})();
class MyPromise {
constructor(executor) {
this._state = PENDING;
this._value = undefined;
this._reason = undefined;
this._fulfilledCallbacks = [];
this._rejectedCallbacks = [];
const _resolve = (value) => {
_nextTick(() => {
if (this._state === PENDING) {
this._state = FULFILLED;
this._value = value;
this._fulfilledCallbacks.forEach(callback => callback());
}
});
};
const _reject = (reason) => {
_nextTick(() => {
if (this._state === PENDING) {
this._state = REJECTED;
this._reason = reason;
this._rejectedCallbacks.forEach(callback => callback());
}
});
};
try {
executor(_resolve, _reject);
} catch (e) {
_reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
const handleCallback = (callback, valueOrReason) => {
_nextTick(() => {
try {
const x = callback(valueOrReason);
_resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
if (this._state === FULFILLED) {
handleCallback(onFulfilled, this._value);
} else if (this._state === REJECTED) {
handleCallback(onRejected, this._reason);
} else if (this._state === PENDING) {
this._fulfilledCallbacks.push(() => handleCallback(onFulfilled, this._value));
this._rejectedCallbacks.push(() => handleCallback(onRejected, this._reason));
}
});
return promise2;
}
// 实现 .catch 方法,实际上就是 .then(null, onRejected) 的语法糖
catch(onRejected) {
return this.then(null, onRejected);
}
// 实现 .finally 方法 (ES2018),无论成功或失败都会执行,且不改变原 Promise 的状态和值
finally(callback) {
return this.then(
value => {
return MyPromise.resolve(callback()).then(() => value);
},
reason => {
return MyPromise.resolve(callback()).then(() => { throw reason; });
}
);
}
// 静态方法
static resolve(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise(resolve => {
resolve(value); // 这里的 resolve 会触发 _resolvePromise 逻辑
});
}
static reject(reason) {
return new MyPromise((_, reject) => {
reject(reason);
});
}
}
function _resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));
}
if (x instanceof MyPromise) {
x.then(resolve, reject);
return;
}
if (x && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
(y) => {
if (called) return;
called = true;
_resolvePromise(promise2, y, resolve, reject);
},
(r) => {
if (called) return;
called = true;
reject(r);
}
);
return;
}
} catch (e) {
if (called) return;
reject(e);
return;
}
}
resolve(x);
}
至此,我们的MyPromise实现已经相当完善,不仅满足了Promises/A+的核心规范,还增加了常用的catch和finally方法,以及Promise.resolve和Promise.reject静态方法。
第七章:测试与验证——Promises/A+ 规范的严格要求
仅仅实现了代码是不够的,我们还需要确保它完全符合Promises/A+规范的所有细则。Promises/A+规范提供了一个官方的测试套件 promises-aplus-tests,专门用于验证Promise实现的合规性。
使用 promises-aplus-tests:
- 安装测试工具:
npm install -g promises-aplus-tests -
创建适配器:
promises-aplus-tests工具需要一个适配器来与你的Promise实现进行交互。这个适配器需要提供一个deferred对象,包含promise、resolve和reject方法。// adapters.js const MyPromise = require('./myPromise'); // 假设你的 MyPromise 在 myPromise.js 中 module.exports = { deferred: function () { let resolve, reject; const promise = new MyPromise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); return { promise: promise, resolve: resolve, reject: reject }; } }; - 运行测试:
promises-aplus-tests adapters.js如果你的实现通过了所有测试(通常是几百个测试用例),那么恭喜你,你的Promise实现是完全符合Promises/A+规范的。如果有些测试失败,测试报告会详细指出失败的规范点,帮助你定位和修复问题。
通过严格的测试,我们可以确保我们的MyPromise不仅在表面上工作,而且在所有边缘情况下都表现出符合规范的预期行为。
尾声:展望未来,掌握核心
至此,我们已经完整地手写实现了一个满足Promises/A+规范的Promise。从理解状态机到.then方法的链式调用,再到精妙的Promise决议过程和微任务调度,我们一步步揭开了Promise内部运作的神秘面纱。
这个过程不仅加深了我们对Promise本身的理解,更重要的是,它锻炼了我们对JavaScript异步编程、事件循环、错误处理以及规范细节的把控能力。掌握这些核心原理,将使我们能够更自信地使用Promise,编写出更健壮、更可维护的异步代码,并为未来学习async/await等更高层级的异步抽象打下坚实的基础。理解Promise,就是理解现代JavaScript异步编程的基石。