一、Promise 链在错误(error)处理中十分强大。当一个 promise 被 reject 时,控制权将移交至最近的 rejection 处理程序(handler)。这在实际开发中非常方便。
二、下面代码中所fetch的 URL 是错的(没有这个网站),.catch对这个 error 进行了处理:

  1. fetch('https://no-such-server.blabla') // rejects
  2. .then(response => response.json())
  3. .catch(err => alert(err)) // TypeError: failed to fetch(这里的文字可能有所不同)

1、正如你所看到的,.catch不必是立即的。它可能在一个或多个.then之后出现。
2、或者,可能该网站一切正常,但响应不是有效的 JSON。捕获所有 error 的最简单的方法是,将.catch附加到链的末尾:

  1. fetch('/article/promise-chaining/user.json')
  2. .then(response => response.json())
  3. .then(user => fetch(`https://api.github.com/users/${user.name}`))
  4. .then(response => response.json())
  5. .then(githubUser => new Promise((resolve, reject) => {
  6. let img = document.createElement('img');
  7. img.src = githubUser.avatar_url;
  8. img.className = "promise-avatar-example";
  9. document.body.append(img);
  10. setTimeout(() => {
  11. img.remove();
  12. resolve(githubUser);
  13. }, 3000);
  14. }))
  15. .catch(error => alert(error.message));

3、通常情况下,这样的.catch根本不会被触发。但是如果上述任意一个 promise 被 reject(网络问题或者无效的 json 或其他),.catch就会捕获它。

隐式 try…catch

一、Promise 的执行者(executor)和 promise 的处理程序(handler)周围有一个“隐式的try..catch”。如果发生异常,它(译注:指异常)就会被捕获,并被视为 rejection 进行处理。
【示例1】

  1. new Promise((resolve, reject) => {
  2. throw new Error("Whoops!"); // 在 executor 周围的“隐式try..catch”自动捕获了 error,并将其变为 rejected promise
  3. }).catch(alert); // Error: Whoops!

1、与下面这段代码工作上完全相同:

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

【示例2】如果我们在.then处理程序(handler)中throw,这意味着 promise 被 rejected,因此控制权移交至最近的 error 处理程序(handler)。

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

【示例3】对于所有的 error 都会发生这种情况,而不仅仅是由throw语句导致的这些 error。例如,一个编程错误:

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

二、最后的.catch不仅会捕获显式的 rejection,还会捕获它上面的处理程序(handler)中意外出现的 error。

再次抛出(Rethrowing)

一、链尾端的.catch的表现有点像try..catch。我们可能有许多个.then处理程序(handler),然后在尾端使用一个.catch处理上面的所有 error。
二、在常规的try..catch中,我们可以分析错误(error),如果我们无法处理它,可以将其再次抛出。对于 promise 来说,这也是可以的。
三、如果我们在.catch中throw,那么控制权就会被移交到下一个最近的 error 处理程序(handler)。如果我们处理该 error 并正常完成,那么它将继续到最近的成功的.then处理程序(handler)。
四、【示例】.catch成功处理了 error:

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

1、这里.catch块正常完成。所以下一个成功的.then处理程序(handler)就会被调用。
五、在下面的例子中,我们可以看到.catch的另一种情况。(*)行的处理程序(handler)捕获了 error,但无法处理它(例如,它只知道如何处理URIError),所以它将其再次抛出:

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

1、执行从第一个.catch()沿着链跳转至下一个(*)。

Promise拒绝事件 / 未处理的 rejection

一、当Promise被拒绝时,会有下文所述的两个事件之一被派发到全局作用域(通常而言,就是window;如果是在web worker中使用的话,就是Worker或者其他worker-based接口)。这两个事件如下:
1、rejectionhandled
(1)当Promise被拒绝、并且在reject函数处理该rejection之后会派发此事件。
2、unhandledrejection
(1)当Promise被拒绝,但没有提供reject函数来处理该rejection时,会派发此事件。
【示例1】忘了在链的尾端附加.catch,error没有被处理

  1. new Promise(function() {
  2. noSuchFunction(); // 这里出现 error(没有这个函数)
  3. })
  4. .then(() => {
  5. // 一个或多个成功的 promise 处理程序(handler)
  6. }); // 尾端没有 .catch!
  7. // 脚本死了,并在控制台(console)中留下了一个信息。
  8. // JavaScript 引擎会跟踪此类 rejection,在这种情况下会生成一个全局的 error。

二、rejectionhandled、unhandledrejection这两种情况中,PromiseRejectionEvent事件都有两个属性,
1、promise属性,该属性指向被驳回的Promise,
2、reason属性,该属性用来说明Promise被驳回的原因。
三、可以通过以上事件(rejectionhandled、unhandledrejection)为Promise失败时提供补偿处理,也有利于调试Promise相关的问题。
四、在每一个上下文中,该处理是全局的,因此不管源码如何,所有的错误都会在同一个处理函数中被捕获并处理。
【示例1】当你使用Node.js时,有些依赖模块可能会有未被处理的rejected promises,这些都会在运行时打印到控制台。可以在自己的代码中捕捉这些信息,然后添加与unhandledrejection相应的处理函数来分析处理,或只是为了让你的输出更整洁。

  1. // 通常此类 error 是无法恢复的,所以我们最好的解决方案是将问题告知用户,并且可以将事件报告给服务器。
  2. window.addEventListener('unhandledrejection', function(event) { // unhandledrejection事件是HTML 标准的一部分
  3. // 这个事件对象有两个特殊的属性:
  4. alert(event.promise); // [object Promise] - 生成该全局 error 的 promise
  5. alert(event.reason); // Error: Whoops! - 未处理的 error 对象
  6. event.preventDefault()
  7. }, false);
  8. new Promise(function() {
  9. throw new Error("Whoops!");
  10. }); // 没有用来处理 error 的 catch

1、调用event的preventDefault()方法是为了告诉JavaScript引擎当Promise被拒绝时不要执行默认操作,默认操作以便会包含把错误打印到控制台,Node就是如此的。
2、理想情况下,在忽略这些事件之前,我们应该检查所有被拒绝的Promise,来确认这不是代码中的bug。

错误传递

一、failureCallbak在Promise链中只有尾部的一次调用

  1. doSomething()
  2. .then(result => doSomethingElse(result))
  3. .then(newResult => doThirdThing(newResult))
  4. .then(finalResult => console.log(`Got the final result: ${finalResult}`))
  5. .catch(failureCallback);

二、一遇到异常抛出,浏览器就会顺着Promise链寻找下一个onRejected失败回调函数或者由.catch()指定的回调函数。
这和以下同步代码的工作原理(执行过程)非常相似。

  1. try {
  2. let result = syncDoSomething();
  3. let newResult = syncDoSomethingElse(result);
  4. let finalResult = syncDoThirdThing(newResult);
  5. console.log(`Got the final result: ${finalResult}`);
  6. } catch(error) {
  7. failureCallback(error);
  8. }

三、在ECMAScript2017标准的async/await语法糖中,这种异步代码的对称性得到了极致的体现
【实例1】以下例子是在Promise的基础上构建的,如:doSomething()与之前的函数是相同的。

  1. async function foo () {
  2. try {
  3. const result = awiat doSomething()
  4. const newResult = await doSomethingElse(result);
  5. const finalResult = await doThirdThing(newResult);
  6. console.log(`Got the final result: ${finalResult}`);
  7. } catch(error) {
  8. failureCallback(error);
  9. }
  10. }

四、通过捕获所有的错误,甚至抛出异常和程序错误,Promise解决了回调地狱的基本缺陷。这对于构建异步操作的基础功能而言是很有必要的。

总结

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