目录

🛟 Promise 错误处理

Promise 链在错误处理中十分强大。当一个 Promise 被 reject 时,控制权将移交至 最近的处理程序

🌰 例子 / 网络请求失败时:

fetch('https://no-such-server.blabla') // rejects
  .then(response => response.json())
  .catch(err => alert(err))
1
2
3

可以看到, .catch 不必是立即的。它可能在一个或多个 .then 之后出现。

所以,一般 .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));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

通常,成功状态的结果不会触发 .catch 。但是如果上述任意一个 Promise 被 reject 出现错误结果, .catch 就会捕获到,

# 隐式的 try...catch

Promise 在执行行为周围和处理程序周围有一个「隐式的 try…catch 」。如果当中的行为发生异常,就会被 catch 捕获被视为 Promise 结果处理。

🌰 例子:

new Promise((resolve, reject) => {
  throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
1
2
3

同理:

new Promise((resolve, reject) => {
  reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
1
2
3

🌰 例子 / 在 处理程序中:

new Promise((resolve, reject) => {
  resolve("ok");
}).then((result) => {
  throw new Error("Whoops!"); // reject 这个 promise
}).catch(alert); // Error: Whoops!
1
2
3
4
5

不仅仅对 throw 抛出的错误有效,代码错误也可以捕获:

new Promise((resolve, reject) => {
  resolve("ok");
}).then((result) => {
  blabla(); // 没有这个函数
}).catch(alert); // ReferenceError: blabla is not defined
1
2
3
4
5

# 再次派出

在平常的 try...catch 结构中,如果最后 catch 不能处理错误,可以将错误再次抛出。对于 Promise 也是可以的。

如果在 .catchthrow ,那么控制权就会被移交到下一个最近的错误处理程序。如果能处理该错误并正常完成,那么它将继续到最近的成功的 .then 处理程序。

🌰 例子 / .catch 成功处理错误:

new Promise((resolve, reject) => {
  throw new Error("Whoops!");
}).catch(function(error) {
  alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
1
2
3
4
5

这里 .catch 块正常完成。所以下一个成功的 .then 处理程序(handler)就会被调用。

🌰 例子 / catch 再次抛出错误:

new Promise((resolve, reject) => {
  throw new Error("Whoops!");
}).catch(function(error) {
  if (error instanceof URIError) {
    // 处理它
  } else {
    alert("Can't handle such error");
    throw error; // 再次抛出此 error 或另外一个 error,执行将跳转至下一个 catch
  }
}).then(function() {
  /* 不在这里运行 */
}).catch(error => { // (**)
  alert(`The unknown error has occurred: ${error}`);
  // 不会返回任何内容 => 执行正常进行
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

没有处理的错误,会从上一个 .catch 跳转到最近的一个 .catch ,中间的 .then 处理程序不会运行。

# 未处理的错误

当一个错误没有被处理。

🌰 例子: 当调用链中的末尾没有处理错误 .catch :

new Promise(function() {
    noSuchFunction(); // 这里出现 error(没有这个函数)
}).then(() => {
    // 一个或多个成功的 promise 处理程序(handler)
  });
1
2
3
4
5

如果出现 error,Promise 的状态将变为 rejected,然后执行应该跳转至最近的错误处理程序。如果没有错误处理程序,因此该错误会卡住,没有代码来处理它。

相对于 try...catch 中没有被处理的错误,会在控制台留下错误信息。Promise 对于没有被处理的错误也是如此。

JavaScript 引擎会跟踪此类 rejection,在这种情况下会生成一个全局的 error。

在浏览器中,可以使用 unhandledrejection 事件来捕获这类 error:

window.addEventListener('unhandledrejection', function(event) {
  // 这个事件对象有两个特殊的属性:
  alert(event.promise); // [object Promise] - 生成该全局 error 的 promise
  alert(event.reason); // Error: Whoops! - 未处理的 error 对象
});

new Promise(function() {
  throw new Error("Whoops!");
}); // 没有用来处理 error 的 catch
1
2
3
4
5
6
7
8
9

这个事件是 HTML 标准 (opens new window) 的一部分。如果出现了一个 error,并且在这儿没有 .catch ,那么 unhandledrejection 处理程序就会被触发,并获取具有 error 相关信息的 event 对象,然后就可以根据这个错误进行后续处理。

通常此类 error 是无法恢复的,所以最好的解决方案是将问题告知用户,并且可以将事件报告给服务器。在 Node.js 等非浏览器环境中,有其他用于跟踪未处理的 error 的方法。

# 总结

  • .catch 处理 promise 中的各种 error:在 reject() 调用中的,或者在处理程序中抛出的 error。
  • 应该将 .catch 准确地放到想要处理 error,并知道如何处理这些 error 的地方。处理程序应该分析 error(可以自定义 error 类来帮助分析)并再次抛出未知的 error(可能它们是编程错误)。
  • 如果没有办法从 error 中恢复的话,不使用 .catch 也可以。
  • 在任何情况下全局 unhandledrejection 事件处理程序(用于浏览器,以及其他环境的模拟),以跟踪未处理的 error 并告知用户(可能还有我们的服务器)有关信息,以使的应用程序永远不会「卡住死掉」。

# 实例

# 不能捕捉到的错误

new Promise(function(resolve, reject) {
  setTimeout(() => {
    throw new Error("Whoops!");
  }, 1000);
}).catch(alert);
1
2
3
4
5

setTimeout 中抛出的错误能够被 .catch 捕捉?

点击查看

不会被触发。Promise 中 执行过程和处理程序周围的隐式 try...catch 处理 所有的同步错误。而 setTimeout 中的错误不是在执行过程中生成的,而是稍后生成的,因此, Promise 无法处理。

📢 上次更新: 2022/09/02, 10:18:16