
promise 构造函数内部的同步执行器(executor)中抛出的异常并不会立即中断整个脚本的执行。这是因为 Promise 内部机制会捕获这些异常,并将 Promise 的状态设置为 rejected,但不会阻止后续代码的执行。理解 Promise 的这种行为对于编写健壮的异步代码至关重要。
当我们在使用 new Promise() 创建 Promise 对象时,会传入一个执行器函数(executor)。这个执行器函数会被立即同步执行。如果在执行器函数内部发生了错误,例如调用了一个未定义的函数,我们可能会期望脚本立即停止执行。然而,实际情况并非如此。
Promise 内部的异常捕获机制
Promise 的设计初衷是为了更好地处理异步操作。为了保证异步操作的可靠性,Promise 内部实现了一个异常捕获机制。当执行器函数抛出异常时,Promise 会捕获这个异常,并将 Promise 的状态设置为 rejected。
具体来说,ecmascript 规范中对 Promise 构造函数有如下定义:
Let completion be Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)).if completion is an abrupt completion, then a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).Return promise.
第 10 步表明,如果执行器函数 (executor) 发生异常(abrupt completion),Promise 会调用 reject 函数,但不会阻止后续代码的执行,而是继续执行第 11 步,返回 Promise 对象。
示例代码与解释
考虑以下代码:
console.log('first'); const promise1 = new Promise((resolve, reject) => { console.log('inside executor'); let what = 1; console.log(what()); // 抛出 TypeError console.log('not reached'); resolve('Hi Guys!'); }); console.log('continues');
这段代码的输出如下:
first inside executor continues Uncaught (in promise) TypeError: what is not a function at index.js:5:15 at new Promise (<anonymous>)
可以看到,尽管 console.log(what()); 抛出了 TypeError,但 console.log(‘continues’); 仍然被执行了。这是因为 Promise 内部捕获了 TypeError,并将 promise1 的状态设置为 rejected,但没有阻止后续代码的执行。
模拟 Promise 构造函数的内部实现
为了更好地理解 Promise 的行为,我们可以模拟 Promise 构造函数的内部实现:
class MyPromise { #state; #resolvedValue; #customers; constructor(executor) { this.#state = "pending"; this.#customers = []; try { executor( (value) => this.#resolve(value), (reason) => this.#reject(reason) ); } catch (err) { // 捕获异常,并允许执行继续 this.#reject(err); } } #reject(reason) { if (this.#state !== "pending") return; // 忽略 this.#state = "rejected"; this.#resolvedValue = reason; this.#broadcast(); // 通知所有 then/catch 回调 } #resolve(value) { if (this.#state !== "pending") return; this.#state = "fulfilled"; this.#resolvedValue = value; this.#broadcast(); } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { this.#customers.push({ resolve: resolve, reject: reject, onFulfilled: onFulfilled, onRejected: onRejected, }); if (this.#state === "fulfilled") { this.#broadcast(); } else if (this.#state === "rejected") { this.#broadcast(); } }); } catch(onRejected) { return this.then(null, onRejected); } #broadcast() { if (this.#state === "fulfilled") { this.#customers.forEach((customer) => { if (customer.onFulfilled) { try { const result = customer.onFulfilled(this.#resolvedValue); customer.resolve(result); } catch (err) { customer.reject(err); } } else { customer.resolve(this.#resolvedValue); } }); } else if (this.#state === "rejected") { this.#customers.forEach((customer) => { if (customer.onRejected) { try { const result = customer.onRejected(this.#resolvedValue); customer.resolve(result); } catch (err) { customer.reject(err); } } else { customer.reject(this.#resolvedValue); } }); } } } // 使用示例 console.log("start"); const myPromise = new MyPromise((resolve, reject) => { console.log("inside executor"); try { let what = 1; console.log(what()); resolve("Success"); } catch (error) { reject(error); } }); myPromise .then((result) => { console.log("then:", result); }) .catch((error) => { console.error("catch:", error); }); console.log("end");
在 MyPromise 类的构造函数中,try…catch 块捕获了执行器函数中可能抛出的异常,并通过 #reject 方法将 Promise 的状态设置为 rejected。
如何处理 Promise 中的异常
虽然 Promise 内部会捕获异常,但我们仍然需要显式地处理这些异常,以避免程序出现未知的错误。通常,我们可以使用 catch() 方法或 async/await 结合 try…catch 块来处理 Promise 中的异常。
-
使用 catch() 方法:
promise1 .then((result) => { console.log('Result:', result); }) .catch((error) => { console.error('Error:', error); }); -
使用 async/await 结合 try…catch 块:
async function myFunction() { try { const result = await promise1; console.log('Result:', result); } catch (error) { console.error('Error:', error); } }
总结
Promise 构造函数内部的异常不会立即中断整个脚本的执行,而是会被 Promise 内部机制捕获,并将 Promise 的状态设置为 rejected。为了保证程序的健壮性,我们需要显式地处理 Promise 中的异常,可以使用 catch() 方法或 async/await 结合 try…catch 块。理解 Promise 的这种行为对于编写可靠的异步代码至关重要。


