有了"承诺"之后,没完成,需要处理
Promise 在错误处理方面非常出色。当 promise 拒绝时,控件跳转到最近的拒绝处理程序。这在实践中很方便。
例如,下面的代码中获取的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添加到chain的末尾:
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。
这不仅发生在executor函数中,也发生在其处理程序中。如果我们抛出一个.then处理程序,这意味着一个被拒绝的承诺,因此控件跳转到最近的错误处理程序。
这里有一个例子:
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不仅能捕获显式的拒绝,还能捕获上述处理程序中的意外错误。
Rethrowing
正如我们已经注意到的,.catch在链的末端类似于try..catch。我们可以有任意多的.then处理程序,然后在末尾使用一个.catch来处理所有处理程序中的错误。
在定期的尝试中…我们可以分析错误,如果不能处理,可能会重新抛出错误。同样的事情也可能发生在承诺上。
如果我们在.catch中抛出,那么控件将转到下一个最近的错误处理程序。如果我们处理错误并正常完成,那么它会继续到下一个成功的。then handler。
在下面的例子中,.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(**)。
Unhandled rejections
当错误没有被处理时会发生什么?例如,我们忘记将.catch添加到链的末尾,就像这样:
new Promise(function() {
noSuchFunction(); // Error here (no such function)
})
.then(() => {
// successful promise handlers, one or more
}); // without .catch at the end!
在出现错误的情况下,承诺被拒绝,执行应该跳转到最近的拒绝处理程序。但是没有。所以错误被“卡住”了。没有代码来处理它。
在实践中,就像代码中的常规未处理错误一样,这意味着某些东西出现了严重的错误。
如果出现了常规错误,但是try..catch没有捕捉到,会发生什么情况?脚本在控制台中结束,并显示一条消息。类似的事情也会发生在未经处理的拒绝承诺上。
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处理程序就会触发,并获取带有错误信息的事件对象,因此我们可以做一些事情。
通常这种错误是不可恢复的,所以我们最好的解决方法是通知用户这个问题,并可能向服务器报告这个事件。
在非浏览器环境中,如Node。还有其他方法可以跟踪未处理的错误。