Promise 链非常适合进行错误处理。当 Promise 被拒绝时,控制权会跳转到最近的拒绝处理程序。这在实践中非常方便。
例如,在下面的代码中,用于 fetch
的 URL 是错误的(没有这样的网站),并且 .catch
处理了错误
fetch('https://no-such-server.blabla') // rejects
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
正如你所见,.catch
不必紧随其后。它可能出现在一个或多个 .then
之后。
或者,网站可能一切正常,但响应不是有效的 JSON。捕获所有错误的最简单方法是将 .catch
附加到链的末尾
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
通常,这样的 .catch
根本不会触发。但如果上述任何一个 Promise 被拒绝(网络问题或无效的 json 或其他任何问题),那么它就会捕获它。
隐式 try…catch
Promise 执行器和 Promise 处理程序的代码周围有一个“不可见的 try..catch
”。如果发生异常,它会被捕获并视为拒绝。
例如,此代码
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
…与以下代码完全相同
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
执行器周围的“不可见的 try..catch
”会自动捕获错误并将其转换为被拒绝的 Promise。
这不仅发生在执行器函数中,也发生在它的处理程序中。如果我们在 .then
处理程序中 throw
,则表示拒绝的 Promise,因此控制权会跳转到最近的错误处理程序。
这里有一个示例
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("Whoops!"); // rejects the promise
}).catch(alert); // Error: Whoops!
这适用于所有错误,而不仅仅是 throw
语句导致的错误。例如,编程错误
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // no such function
}).catch(alert); // ReferenceError: blabla is not defined
最终的 .catch
不仅会捕获显式拒绝,还会捕获上述处理程序中的意外错误。
重新抛出
正如我们已经注意到的,链末尾的 .catch
类似于 try..catch
。我们可以拥有任意多个 .then
处理程序,然后在末尾使用单个 .catch
来处理所有处理程序中的错误。
在常规的 try..catch
中,我们可以分析错误,如果无法处理,则可能重新抛出它。对于 Promise 来说,也可以这样做。
如果我们在 .catch
中 throw
,则控制权将转到下一个最近的错误处理程序。如果我们处理了错误并正常完成,则它将继续到下一个最近的成功的 .then
处理程序。
在下面的示例中,.catch
成功处理了错误
// the execution: catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
此处,.catch
块正常结束。因此,将调用下一个成功的 .then
处理程序。
在下面的示例中,我们看到了 .catch
的另一种情况。处理程序 (*)
捕获了错误,但无法处理它(例如,它只知道如何处理 URIError
),因此再次抛出它
// the execution: catch -> catch
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
throw error; // throwing this or another error jumps to the next catch
}
}).then(function() {
/* doesn't run here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
执行从第一个 .catch
(*)
跳到链中的下一个 (**)
。
未处理的拒绝
当错误未处理时会发生什么?例如,我们忘记将 .catch
附加到链的末尾,如下所示
new Promise(function() {
noSuchFunction(); // Error here (no such function)
})
.then(() => {
// successful promise handlers, one or more
}); // without .catch at the end!
如果发生错误,则 Promise 将被拒绝,并且执行应跳转到最近的拒绝处理程序。但没有这样的处理程序。因此,错误“卡住了”。没有代码可以处理它。
在实践中,就像代码中未处理的常规错误一样,这意味着出现了严重错误。
当发生常规错误且未被 try..catch
捕获时会发生什么?脚本会终止,并在控制台中显示一条消息。未处理的 Promise 拒绝也会发生类似的情况。
JavaScript 引擎会跟踪此类拒绝,并在这种情况下生成全局错误。如果你运行上面的示例,可以在控制台中看到它。
在浏览器中,我们可以使用事件 unhandledrejection
捕获此类错误
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
该事件是 HTML 标准 的一部分。
如果发生错误,并且没有 .catch
,则 unhandledrejection
处理程序将触发,并获取包含错误信息的 event
对象,以便我们可以采取一些措施。
通常,此类错误是不可恢复的,因此我们最好的办法是向用户告知问题,并可能向服务器报告该事件。
在非浏览器环境(如 Node.js)中,还有其他方法来跟踪未处理的错误。
摘要
.catch
处理各种 Promise 中的错误:无论是reject()
调用,还是在处理程序中抛出的错误。- 如果给出了第二个参数(即错误处理程序),则
.then
也以相同的方式捕获错误。 - 我们应该将
.catch
准确地放在我们希望处理错误并知道如何处理它们的位置。处理程序应分析错误(自定义错误类有所帮助),并重新抛出未知错误(可能是编程错误)。 - 如果无法从错误中恢复,则不使用
.catch
也是可以的。 - 无论如何,我们都应该有
unhandledrejection
事件处理程序(对于浏览器,以及其他环境的类似程序)来跟踪未处理的错误,并告知用户(以及我们的服务器),以便我们的应用程序永远不会“突然死亡”。
评论
<code>
标记,要插入多行代码,请使用<pre>
标记将其包装起来,要插入 10 行以上的代码,请使用沙箱(plnkr、jsbin、codepen…)