各位观众老爷,晚上好!我是今晚的主讲人,咱们今天聊聊 JavaScript 中 Promise 链式调用里的那些个坑,特别是关于错误捕获和 Unhandled Rejection 的事儿。 这玩意儿,用好了是神兵利器,用不好那就是埋雷专家,一不小心就炸得你怀疑人生。
Promise 链式调用:爽,但也要小心翻车
Promise 这东西,当初设计出来就是为了解决回调地狱的,它让异步操作看起来更像同步代码,链式调用更是让代码变得优雅无比。 但是,优雅的背后往往隐藏着危机,错误处理就是其中之一。
先来回顾一下 Promise 的基本结构:
new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = Math.random() > 0.5; // 模拟成功或失败
if (success) {
resolve("操作成功!");
} else {
reject("操作失败!");
}
}, 1000);
})
.then(value => {
console.log("then 1: ", value);
return "then 1 返回值";
})
.then(value => {
console.log("then 2: ", value);
return "then 2 返回值";
})
.catch(error => {
console.error("catch: ", error);
});
这段代码,相信大家已经看过无数遍了。 new Promise
创建一个 Promise 实例,里面执行异步操作,resolve
表示成功,reject
表示失败。 then
方法处理成功的结果,catch
方法处理失败的结果。
错误捕获:抓到它,别让它跑了!
在 Promise 链中,错误的捕获至关重要。 如果某个 Promise 抛出了错误,而你没有及时捕获,那就会导致 Unhandled Rejection,到时候浏览器控制台会给你一堆红字,让你一脸懵逼。
-
.catch()
的位置很重要catch
方法用于捕获 Promise 链中任何地方抛出的错误。 它的位置决定了它能捕获哪些错误。-
放在链尾: 捕获整个链中的任何错误。
new Promise((resolve, reject) => { setTimeout(() => { reject("Promise 1 失败!"); }, 500); }) .then(value => { console.log("then 1: ", value); }) .then(value => { console.log("then 2: ", value); }) .catch(error => { console.error("链尾 catch: ", error); // 这里会捕获到 "Promise 1 失败!" });
-
放在中间: 捕获它之前的所有错误,之后的错误由后续的
catch
或链尾的catch
捕获。new Promise((resolve, reject) => { setTimeout(() => { reject("Promise 1 失败!"); }, 500); }) .catch(error => { console.error("中间 catch: ", error); // 这里会捕获到 "Promise 1 失败!" return "catch 中处理了错误"; // 返回一个 resolved 的 Promise }) .then(value => { console.log("then 1: ", value); // 这里会输出 "catch 中处理了错误" throw new Error("then 1 中抛出错误"); }) .catch(error => { console.error("链尾 catch: ", error); // 这里会捕获到 "Error: then 1 中抛出错误" });
记住,每个
catch
都会返回一个resolved
状态的 Promise。 这意味着,即使你在catch
里处理了错误,后续的then
仍然会执行。 如果你想阻止后续的then
执行,需要在catch
中抛出一个新的错误。 -
-
try...catch
和 Promise 的结合有时候,你需要在
then
方法中执行一些可能抛出同步异常的代码。 这时候,就可以使用try...catch
语句。new Promise((resolve, reject) => { resolve("操作成功!"); }) .then(value => { try { console.log("then 1: ", value); throw new Error("then 1 中抛出同步错误"); } catch (error) { console.error("try...catch: ", error); // 这里捕获到 "Error: then 1 中抛出同步错误" // 可以选择 reject 这个 Promise,将错误传递给链尾的 catch return Promise.reject(error); // 关键! } }) .then(value => { console.log("then 2: ", value); }) .catch(error => { console.error("链尾 catch: ", error); // 这里会捕获到 "Error: then 1 中抛出同步错误" });
注意,如果在
try...catch
中捕获了错误,并且想要将错误传递给链尾的catch
,必须使用Promise.reject(error)
将 Promise 的状态变为 rejected。 如果只是简单地return
,Promise 的状态仍然是 resolved,后续的then
仍然会执行。 -
finally()
方法finally()
方法无论 Promise 的状态是 resolved 还是 rejected 都会执行。 它通常用于执行一些清理工作,比如关闭数据库连接、释放资源等。new Promise((resolve, reject) => { const success = Math.random() > 0.5; setTimeout(() => { if (success) { resolve("操作成功!"); } else { reject("操作失败!"); } }, 500); }) .then(value => { console.log("then: ", value); }) .catch(error => { console.error("catch: ", error); }) .finally(() => { console.log("finally: 无论成功或失败都会执行"); });
需要注意的是,
finally()
方法不会接收任何参数,也无法改变 Promise 的状态。 如果在finally()
方法中抛出错误,这个错误会被忽略,并且 Promise 链会继续执行。 所以,尽量避免在finally()
中执行可能抛出错误的代码。
Unhandled Promise Rejection:防患于未然
Unhandled Promise Rejection 发生在 Promise 被 reject,但没有提供任何 catch
处理程序时。 这会导致浏览器控制台输出错误信息,并且可能会导致程序崩溃。
-
全局事件监听
现代浏览器提供了一个全局事件
unhandledrejection
,可以用来监听未处理的 Promise Rejection。window.addEventListener('unhandledrejection', event => { console.error('Unhandled rejection:', event.reason); // 可以将错误信息发送到服务器,进行监控 // ... event.preventDefault(); // 阻止默认行为,防止控制台输出错误 });
通过监听
unhandledrejection
事件,可以捕获到所有未处理的 Promise Rejection,并进行相应的处理,比如记录日志、发送警报等。event.reason
包含了 rejection 的原因,通常是一个错误对象或字符串。event.promise
包含了被 reject 的 Promise 对象。
调用event.preventDefault()
可以阻止浏览器默认的处理方式,避免在控制台输出错误信息。 但是,强烈建议不要随意阻止默认行为,除非你已经对错误进行了充分的处理,并且确定不需要浏览器再进行任何处理。 -
避免 Unhandled Rejection 的最佳实践
避免 Unhandled Rejection 的最佳方法是在 Promise 链的末尾添加一个
catch
方法,确保所有可能的错误都被捕获。// 这是一个容易产生 Unhandled Rejection 的例子 new Promise((resolve, reject) => { setTimeout(() => { reject("操作失败!"); }, 500); }); // 正确的做法是在链尾添加 catch new Promise((resolve, reject) => { setTimeout(() => { reject("操作失败!"); }, 500); }) .catch(error => { console.error("catch: ", error); });
另外,在使用
async/await
时,也要注意使用try...catch
语句来捕获可能抛出的错误。async function fetchData() { try { const response = await fetch('https://example.com/api/data'); const data = await response.json(); return data; } catch (error) { console.error("fetchData 错误: ", error); throw error; // 重新抛出错误,让调用者处理 } } async function processData() { try { const data = await fetchData(); console.log("数据: ", data); } catch (error) { console.error("processData 错误: ", error); } } processData();
总结:Promise 错误处理的葵花宝典
为了方便大家记忆,我把 Promise 错误处理的要点总结成一个表格:
机制 | 作用 | 注意事项 |
---|---|---|
.catch() |
捕获 Promise 链中的错误。 | 位置很重要,决定了它能捕获哪些错误。 每个 catch 都会返回一个 resolved 状态的 Promise。 |
try...catch |
捕获 then 方法中可能抛出的同步异常。 |
如果要将错误传递给链尾的 catch ,必须使用 Promise.reject(error) 将 Promise 的状态变为 rejected。 |
.finally() |
无论 Promise 的状态是 resolved 还是 rejected 都会执行。 | 用于执行清理工作,比如关闭数据库连接、释放资源等。 尽量避免在 finally() 中执行可能抛出错误的代码。 |
unhandledrejection |
监听未处理的 Promise Rejection。 | 可以捕获到所有未处理的 Promise Rejection,并进行相应的处理,比如记录日志、发送警报等。 谨慎使用 event.preventDefault() ,除非你已经对错误进行了充分的处理。 |
async/await + try...catch |
async/await 是 Promise 的语法糖,也需要使用 try...catch 来捕获错误。 |
确保 await 语句放在 try 块中。 如果需要在 catch 中处理错误并继续传递,可以使用 throw error 重新抛出错误。 |
避免 Unhandled Rejection | 确保 Promise 链的末尾有一个 catch 方法。 |
这是一种防御性编程的措施,可以避免由于疏忽而导致的 Unhandled Rejection。 |
一些小贴士
- 错误信息要清晰明了: 在 reject Promise 时,尽量提供清晰明了的错误信息,方便调试。
- 日志记录要到位: 记录 Promise 的 resolved 和 rejected 信息,可以帮助你追踪问题。
- 多做单元测试: 针对 Promise 链进行单元测试,可以及早发现错误。
总结的总结
Promise 的错误处理,说难不难,说简单也不简单。 关键在于理解 Promise 的运行机制,掌握各种错误处理方法,并且养成良好的编程习惯。 只有这样,才能写出健壮可靠的 JavaScript 代码。
好了,今天的讲座就到这里。 希望大家以后在 Promise 的世界里,少踩坑,多拿分! 谢谢大家!