JavaScript错误处理完全指南

footballboy
发布于 2021-4-6 09:41
浏览
0收藏

JavaScript错误处理完全指南-鸿蒙开发者社区
本文将介绍如何处理同步和异步 JavaScript 代码中的错误和异常。建议收藏!

 

本文最初发布于 valentinog.com 网站,经原作者授权由 InfoQ 中文站翻译并分享。

 

什么是编程中的错误?

 

在我们的程序中,事物并非总是一帆风顺的。

特别是在某些情况下,我们可能希望 停止程序或在发生意外错误时通知用户。

例如:

  • 程序试图打开一个不存在的文件。
  • 网络连接断开。
  • 用户输入了无效的内容。
    在所有这些情况下,我们程序员都会创建 错误,或者让编程引擎为我们创建一些错误。

在创建错误之后,我们可以向用户发送一条消息,或者完全停止执行。

 

JavaScript 中有什么错误?

 

JavaScript 中的一个错误是一个对象,错误会被 抛出 以暂停程序。

要在 JavaScript 中创建一个新错误,我们需要调用适当的 构造函数。例如,要创建一个新的泛型错误,我们可以执行以下操作:

const err = new Error("Something bad happened!");

创建一个错误对象时,也可以省略 new 关键字:

const err = Error("Something bad happened!");

创建后,错误对象将显示三个属性:

 

  • message:包含错误消息的字符串。
  • name:错误的类型。
  • stack:函数执行的堆栈跟踪。
    例如,如果我们创建一个新的 TypeError 对象,带有适当的消息,该 message 将携带实际的错误字符串,而 name 将为“TypeError”:
    const wrongType = TypeError("Wrong type given, expected number");
    wrongType.message; // "Wrong type given, expected number"
    wrongType.name; // "TypeError"​

Firefox 还实现了一些非标准属性,如 columnNumber、filename 和 lineNumber。

 

JavaScript 中的错误类型

 

JavaScript 中有很多错误类型,包括:

  • Error
  • EvalError
  • InternalError
  • RangeError
  • ReferenceError
  • SyntaxError
  • TypeError
  • URIError


请记住,所有这些错误类型都是 实际的构造函数,旨在返回一个新的错误对象。

在代码中,你将主要使用 Error 和 TypeError 这两种最常见的类型来创建自己的错误对象。

一般来说,大多数错误将直接来自 JavaScript 引擎,例如 InternalError 或 SyntaxError。

当你尝试重赋值 const 时,会发生 TypeError:

const name = "Jules";
name = "Caty";
// TypeError: Assignment to constant variable.

 

当你的语言关键字拼写错误时,会发生 SyntaxError:

va x = '33';
// SyntaxError: Unexpected identifier

 

或者,当你在错误的地方使用保留的关键字时,例如在一个 async 函数外部 await:

function wrong(){
    await 99;
}
wrong();
// SyntaxError: await is only valid in async function

 

当我们在页面中选择不存在的 HTML 元素时,也会发生 TypeError

Uncaught TypeError: button is null

除了这些传统的错误对象外,JavaScript 中很快还会有 AggregateError 对象。AggregateError 可以很容易地将多个错误包装在一起,后文会具体介绍。


除了这些内置错误外,在浏览器中我们还可以找到:

  • DOMException。
  • DOMError,已弃用,如今不再使用。
    DOMException 是与 WebAPI 相关的一系列错误。当我们在浏览器中做蠢事时它们就会被抛出,例如
    document.body.appendChild(document.cloneNode(true));​

结果:

Uncaught DOMException: Node.appendChild: May not add a Document as a child

 

有关完整列表,请参见 MDN 上的这一页面

 

什么是异常?

 

多数开发人员认为错误和异常是同一回事。实际上,一个错误对象只有在被抛出时才成为异常。

要在 JavaScript 中抛出一个异常,我们使用 throw,然后是错误对象:

const wrongType = TypeError("Wrong type given, expected number");
throw wrongType;

 

缩写形式更常见,在大多数代码库中你都可以找到:

throw TypeError("Wrong type given, expected number");

 

throw new TypeError("Wrong type given, expected number");

 

不太可能将异常抛出到函数或条件块之外。相反,考虑以下示例:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}

 

在这里,我们检查这个函数参数是否为一个字符串。如果不是,我们抛出一个异常。从技术上讲,你可以在 JavaScript 中抛出任何内容,而不仅仅是错误对象:

 throw Symbol();
throw 33;
throw "Error!";
throw null

但最好避免这些事情,始终抛出正确的错误对象,而不是基元。这样,你就可以在代码库中保持错误处理的一致性。其他团队成员就能一直在错误对象上访问 error.message 或 error.stack。

 

当我们抛出异常时会发生什么?

 

异常就像在上升的电梯:一旦抛出一个,它就会在程序栈中冒泡,除非它在某个地方被捕获。

考虑以下代码:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}
toUppercase(4);

 

如果你在浏览器或 Node.js 中运行此代码,程序将停止并报告错误:

Uncaught TypeError: Wrong type given, expected a string
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

此外,你可以看到发生错误的具体代码行。这个报告是一个 堆栈跟踪,对于跟踪代码中的问题很有帮助。


堆栈跟踪的顺序是从底到顶的。所以在这里:

toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

 

我们可以说:

 

  • 第 9 行中的代码调用了 toUppercase
  • toUppercase 在第 3 行爆炸了
    除了在浏览器的控制台中看到这个堆栈跟踪外,你还可以在错误对象的 stack 属性上访问它。

如果这个异常 未捕获,即程序员没有采取任何措施来捕获它,则程序将崩溃。

在何时何地捕获代码中的异常取决于具体的用例。

 

例如,你可能想在堆栈中传播一个异常,以使程序完全崩溃。出现致命的错误时可能就会是这种情况,因为停止程序比处理无效数据更安全。

介绍了基础知识之后,现在我们来研究 同步和异步 JavaScript 代码中的错误和异常处理。

 

同步错误处理

 

同步代码在大多数情况下很简单,它的错误处理也是如此。

 

常规函数的错误处理

 

同步代码的执行顺序和代码的编写顺序一致。再来看前面的示例:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}
toUppercase(4);

 

在这里,引擎调用并执行 toUppercase。所有这些都是 同步 发生的。要 捕获 由此类同步函数引发的异常,我们可以使用 try/catch/finally:

try {
  toUppercase(4);
} catch (error) {
  console.error(error.message);
  // or log remotely
} finally {
  // clean up
}

通常,try 处理最简单的场景,或可能抛出错误的函数调用。catch 则会 捕获实际的异常。它 接收错误对象,我们可以检查该错误对象(并将其远程发送到生产环境中的某些记录器)。


另一方面,无论函数的结果如何,finally 语句都会运行:无论是失败还是成功,final 内部的任何代码都将运行。

记住:try/catch/finally 是一个 同步 结构:它现在具有捕获来自异步代码异常的方法。

 

生成器函数的错误处理


JavaScript 中的生成器(generator)函数是一种特殊的函数。

除了在其内部作用域和消费者之间提供 双向通信通道 外,它可以 随意暂停和恢复。

要创建一个生成器函数,我们在 function 关键字后加一个星号 *:

function* generate() {
//
}

 

一旦进入函数,我们就可以使用 yield 来返回值:

function* generate() {
  yield 33;
  yield 99;
}

 

生成器函数的返回值 是 迭代器(iterator)对象。为了 从生成器中提取值,我们可以使用两种方法:

 

  • 在迭代器对象上调用 next()。
  • for...of 的 迭代。
    以我们的示例为例,要从生成器获取值,我们可以这样做:
    function* generate() {
      yield 33;
      yield 99;
    }
    const go = generate();​

 

当我们调用生成器函数时,go 成为我们的迭代器对象。从现在开始,我们可以调用 go.next() 来推进执行:

function* generate() {
  yield 33;
  yield 99;
}
const go = generate();
const firstStep = go.next().value; // 33
const secondStep = go.next().value; // 99

 

生成器也有另一种工作机制:它们可以接受调用者返回的值和异常。除了 next() 之外,从生成器返回的迭代器对象还具有 throw() 方法。


使用这种方法,我们可以将异常注入生成器来暂停程序:

function* generate() {
  yield 33;
  yield 99;
}
const go = generate();
const firstStep = go.next().value; // 33
go.throw(Error("Tired of iterating!"));
const secondStep = go.next().value; // never reached

 

要捕获此类错误,你可以使用 try/catch 将代码包装在生成器中(如果需要的话也可以用 finally):

function* generate() {
  try {
    yield 33;
    yield 99;
  } catch (error) {
    console.error(error.message);
  }
}

生成器函数还可以向外部抛出异常。捕获这些异常的机制与捕获同步异常的机制相同:try/catch/finally。


这是一个从外部使用 for...of 消费的生成器函数的示例:

function* generate() {
  yield 33;
  yield 99;
  throw Error("Tired of iterating!");
}
try {
  for (const value of generate()) {
    console.log(value);
  }
} catch (error) {
  console.error(error.message);
}
/* Output:
33
99
Tired of iterating!
*/

在这里,我们迭代 try 块中的 happy path。如果发生任何异常,我们将使用 catch 停止它。


异步错误处理


JavaScript 本质上是同步的,是一种单线程语言。

浏览器引擎之类的主机环境使用许多 WebAPI 增强了 JavaScript,以同外部系统交互并处理 I/O 相关联的操作。

浏览器中的异步性示例包括超时、事件和 Promise。

异步世界中的错误处理 与同步世界是不一样的。

我们来看一些例子。

 

计时器错误处理


开始探索 JavaScript 时,在学习了 try/catch/finally 之后,你可能会想将它放在任何代码块中。

考虑以下代码段:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

 

此函数将在大约 1 秒钟后抛出错误。处理此异常的正确方法是什么?以下示例 不起作用:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

正如我们所说,try/catch 是同步的。另一方面,我们有 setTimeout,这是一个用于计时器(timer)的浏览器 API。到传递给 setTimeout 的回调运行时,我们的 try/catch早就没了。该程序将崩溃,因为我们无法捕获异常。


它们走的是两条不同的路径:

Track A: --> try/catch
Track B: --> setTimeout --> callback --> throw

如果我们不想让程序崩溃,为了正确处理错误,我们必须在 setTimeout 的回调内移动 try/catch。但是,这种方法在大多数情况下没有多大意义。稍后我们将看到,使用 Promises 进行异步错误处理可提供更好的开发体验。


事件错误处理


文档对象模型(DOM)中的 HTML 节点连接到 EventTarget,EventTarget 是浏览器中任何事件发射器(emitter)的公共祖先。

这意味着我们可以侦听页面中任何 HTML 元素上的事件:

https://www.valentinog.com/blog/event/#how-does-event-driven-applies-to-javascript-in-the-browser

(Node.js 会在未来版本中支持 EventTarget)。

DOM 事件的错误处理机制遵循异步 WebAPI 的模式。

考虑以下示例:

const button = document.querySelector("button");
button.addEventListener("click", function() {
  throw Error("Can't touch this button!");
});

 

在这里,单击按钮后立即抛出一个异常。我们如何捕获它呢?这个模式 不起作用,也不会阻止程序崩溃:

const button = document.querySelector("button");
try {
  button.addEventListener("click", function() {
    throw Error("Can't touch this button!");
  });
} catch (error) {
  console.error(error.message);
}

与前面带有 setTimeout 的示例一样,传递给 addEventListener 的任何回调均 异步 执行:

Track A: --> try/catch
Track B: --> addEventListener --> callback --> throw

 

如果我们不希望程序崩溃,则要正确处理错误,我们必须在 addEventListener 的回调中移动 try/catch。但同样,这样做几乎没有任何价值。


与 setTimeout 一样,异步代码路径抛出的异常 无法从外部捕获,这将使程序崩溃。

在下一部分中,我们将了解如何使用 Promises 和 async/await 简化异步代码的错误处理。

 

onerror 是什么情况?


HTML 元素有许多事件处理器,例如 onclick、onmouseenter、onchange 等。

还有 onerror,但它与 throw 之类是无关的。

每当<img>标签或<script>之类的 HTML 元素遇到不存在的资源时,onerror 事件处理器都会触发。

考虑以下示例:

// omitted
<body>
<img src="nowhere-to-be-found.png" alt="So empty!">
</body>
// omitted

 

当访问缺少资源或不存在资源的 HTML 文档时,浏览器的控制台会记录以下错误:

GET http://localhost:5000/nowhere-to-be-found.png
[HTTP/1.1 404 Not Found 3ms]

在 JavaScript 中,我们可以使用适当的事件处理器来“捕获”此错误:

const image = document.querySelector("img");
image.onerror = function(event) {
  console.log(event);
};

更好的是:

const image = document.querySelector("img");
image.addEventListener("error", function(event) {
  console.log(event);
});

此模式可以方便地 加载替代资源来代替丢失的图像或脚本。但是请记住:onerror 与 throw 或 try/catch 无关。


使用 Promise 处理错误


为了说明用 Promise 处理错误的机制,我们将“Promise”我们的一个原始示例。调整以下函数:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}
toUppercase(4);

 

这里我们不会返回简单的字符串或异常,而是分别使用 Promise.reject 和 Promise.resolve 处理错误和成功情况:

function toUppercase(string) {
  if (typeof string !== "string") {
    return Promise.reject(TypeError("Wrong type given, expected a string"));
  }
  const result = string.toUpperCase();
  return Promise.resolve(result);
}

从技术上讲这段代码中没有异步的内容,但它很好地展示了具体的机制)。


现在函数已“Promise 化”,我们可以附加 then 来消费结果,并 catch 以 处理被拒绝的 Promise:

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message));

代码会记录:

Wrong type given, expected a string

在 Promise 的世界中,catch 是用于处理错误的结构。除了 catch 和 then,我们也有 finally,类似于 try/catch 中的 finally。


作为其同步的“相对”,Promise 的 finally无论Promise 的结果如何都会运行:

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message))
  .finally(() => console.log("Run baby, run"));

谨记,传递给 then/catch/finally 的任何回调都是由微任务队列异步处理的。它们是 微任务,优先于事件和计时器等宏任务。


Promise,错误和抛出


作为 拒绝 Promise 的最佳实践,提供错误对象很方便:

Promise.reject(TypeError("Wrong type given, expected a string"));

这样,你可以在代码库中保持错误处理的一致性。其他团队成员总是能访问 error.message,更重要的是你可以检查堆栈跟踪。除了 Promise.reject,我们还可以通过 抛出 异常来退出 Promise 链。


考虑以下示例:

Promise.resolve("A string").then(value => {
  if (typeof value === "string") {
    throw TypeError("Expected a number!");
  }
});

 

我们用一个字符串解析一个 Promise,然后 Promise 链会立刻被 throw 断开。要停止异常传播,我们照常使用 catch:

Promise.resolve("A string")
  .then(value => {
    if (typeof value === "string") {
      throw TypeError("Expected a number!");
    }
  })
  .catch(reason => console.log(reason.message));

 

这种模式在 fetch 中很常见,我们在 fetch 中检查响应对象以查找错误:

fetch("https://example-dev/api/")
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response.json();
  })
  .then(json => console.log(json));

在这里,异常可以被 catch 拦截。如果我们失败了,或者决定不在这里捕获它,那么 异常就可以在堆栈中冒泡了。这本身并不坏,但是不同的环境对未捕获的拒绝的反应是不同的。


例如,将来的 Node.js 将使任何未处理 Promise 拒绝的程序崩溃:

DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

所以最好捕获它们!

 

“Promise 化”计时器的错误处理


使用计时器或事件无法捕获从回调抛出的异常。我们在上一节中看到了一个示例:

function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}
// DOES NOT WORK
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

 

Promise 提供的一个解决方案是代码的“Promise 化”。具体来说,我们用 Promise 包装计时器:

function failAfterOneSecond() {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(Error("Something went wrong!"));
    }, 1000);
  });
}

 

通过 reject,我们启动了一个 Promise 拒绝,带有一个错误对象。这时,我们可以使用 catch 处理异常:

failAfterOneSecond().catch(reason => console.error(reason.message));

注意:通常使用 value 作为 Promise 的返回值,并使用 reason 作为拒绝的返回对象。


Node.js 有一个名为 promisify 的实用程序,可简化旧式回调 API 的“Promise 化”。

https://nodejs.org/api/util.html#util_util_promisify_original

 

Promise.all 中的错误处理


静态方法 Promise.all 接收一个 Promise 数组,并从所有解析中的 Promise 返回一个结果数组:

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.resolve("All good here too!");
Promise.all([promise1, promise2]).then((results) => console.log(results));
// [ 'All good!', 'All good here too!' ]

如果这些 Promise 中的任何一个被拒绝,Promise.all 都会拒绝,并返回第一个被拒绝的 Promise 中的错误。为了在 Promise.all 中处理这些情况,我们像上一节中一样使用 catch:

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
const promise3 = Promise.reject(Error("Bad day ..."));
Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message));

同样,无论 Promise.all 的结果如何都要运行函数的话,我们可以使用 finally:

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

 

Promise.any 中的错误处理

 

我们可以将 Promise.any(Firefox>79,Chrome>85)视为 Promise.all 的反面。

即使数组中只有一个 Promise 拒绝,Promise.all 也会返回失败;而 Promise.any 始终为我们提供第一个已解析的 Promise(如果存在于数组中),不管发生了什么拒绝。

如果 所有 传递给 Promise.any 的 Promise 都拒绝,则产生的错误是 AggregateError。考虑以下示例:

Promise.all([promise1, promise2, promise3])
  .then(results => console.log(results))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));

在这里,我们使用 catch 处理错误。此代码的输出是:

const promise1 = Promise.reject(Error("No good, sorry!"));
const promise2 = Promise.reject(Error("Bad day ..."));
Promise.any([promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

 

AggregateError 对象具有与基础的 Error 相同的属性,外加一个 errors 属性:

AggregateError: No Promise in Promise.any was resolved
Always runs!

此属性是由拒绝产生的各个错误组成的数组:

//
  .catch(error => console.error(error.errors))
//

 

Promise.race 中的错误处理

 

静态方法 Promise.race 接收一个 Promise 数组:

[Error: "No good, sorry!, Error: "Bad day ..."]


结果是 第一个赢得“比赛”的 Promise。那拒绝呢?如果拒绝的 Promise 不是第一个出现在输入数组中的对象,则 Promise.race 解析:

const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");
Promise.race([promise1, promise2]).then(result => console.log(result));
// The first!


如果 拒绝出现在数组的第一个元素中,则 Promise.race 拒绝,且我们必须捕获这个拒绝:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");
Promise.race([rejection, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error.message));
// Ouch!

 

Promise.allSettled 中的错误处理

 

Promise.allSettled 是 ECMAScript 2020 加入的。

使用这种静态方法没有什么要处理的,因为 即使一个或多个输入 Promise 拒绝,结果始终是一个已解析的 Promise。

考虑以下示例:

const promise1 = Promise.resolve("Good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
Promise.allSettled([promise1, promise2])
  .then(results => console.log(results))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));


我们传递给 Promise.allSettled 一个由两个 Promise 组成的数组:一个已解析,另一个被拒绝。在这种情况下,catch 将永远不会启用。于是会运行 finally。


代码的结果记录在 then 中,如下:

[
  { status: 'fulfilled', value: 'Good!' },
  {
    status: 'rejected',
    reason: Error: No good, sorry!
  }
]


async/await 的错误处理

 

JavaScript 中的 async/await 表示异步函数,但是从读者的角度来看,它们也拥有同步函数的所有 可读性。

为简单起见,我们将先前的同步函数设为 Uppercase,并在 function 关键字之前放置 async,以将其转换为异步函数:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}

只需在函数前面加上 async 前缀,我们就可以使函数 返回一个 Promise。这意味着我们可以在函数调用之后来一串 then、catch 和 finally:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}
toUppercase("abc")
  .then(result => console.log(result))
  .catch(error => console.error(error.message))
  .finally(() => console.log("Always runs!"));


当我们从一个 async 函数中抛出异常时,异常将成为底层 Promise 被拒绝的原因。


可以使用 catch 从外部拦截任何错误。

最重要的是,除了这种样式外,我们还可以使用try/catch/finally,就像我们使用同步函数时所做的一样。

在下面的示例中,我们从另一个函数 consumer 调用 toUppercase,前者方便地用 try/catch/finally 将函数调用包装起来:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }
  return string.toUpperCase();
}
async function consumer() {
  try {
    await toUppercase(98);
  } catch (error) {
    console.error(error.message);
  } finally {
    console.log("Always runs!");
  }
}
consumer(); // Returning Promise ignored


输出是:

Wrong type given, expected a string
Always runs!


同一主题的资料:如何从 JavaScript 中的 async 函数抛出错误?

https://www.valentinog.com/blog/throw-async/

 

异步生成器的错误处理


JavaScript 中的 异步生成器 是 能够生成 Promise 而非简单值的生成器函数。

它们将生成器函数与 async 结合在一起。结果是一个生成器函数,其迭代器对象将一个 Promise 暴露给消费者。

要创建一个异步生成器,我们用星号 * 声明一个生成器函数,加一个 async 前缀:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}


此处的错误处理规则也是和 Promise 一样的。在异步生成器中 throw 将导致一个 Promise 拒绝,我们使用 catch 拦截它。要从异步生成器拉出 Promise,我们可以使用两种方法:

  • then 处理器。
  • async 迭代。


从上面的示例中,我们可以肯定地知道在前两个 yield 之后会有一个异常。也就是说我们可以:

const go = asyncGenerator();
go.next().then(value => console.log(value));
go.next().then(value => console.log(value));
go.next().catch(reason => console.error(reason.message));


代码输出是:

{ value: 33, done: false }
{ value: 99, done: false }
Something went wrong!


另一种方法是使用 for await...of 的 async 迭代。要使用 async 迭代,我们需要使用一个 async 函数包装这个消费者。


下面是完整的示例:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}
async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}
consumer();


与 async/await 一样,我们使用 try/catch 处理任何潜在的异常:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}
async function consumer() {
  try {
    for await (const value of asyncGenerator()) {
      console.log(value);
    }
  } catch (error) {
    console.error(error.message);
  }
}
consumer();


代码输出是:

33
99
Something went wrong!


从异步生成器函数返回的迭代器对象也有一个 throw() 方法,非常像它的同步形式。在此处的迭代器对象上调用 throw() 不会抛出异常,而是一个 Promise 拒绝:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  yield 11;
}
const go = asyncGenerator();
go.next().then(value => console.log(value));
go.next().then(value => console.log(value));
go.throw(Error("Let's reject!"));
go.next().then(value => console.log(value)); // value is undefined


要从外部处理这种情况,我们可以执行以下操作:

go.throw(Error("Let's reject!")).catch(reason => console.error(reason.message));


但是,请不要忘记迭代器对象 throw()在生成器内部 发送异常。也就是说我们还可以应用以下模式:

async function* asyncGenerator() {
  try {
    yield 33;
    yield 99;
    yield 11;
  } catch (error) {
    console.error(error.message);
  }
}
const go = asyncGenerator();
go.next().then(value => console.log(value));
go.next().then(value => console.log(value));
go.throw(Error("Let's reject!"));
go.next().then(value => console.log(value)); // value is undefined

 

总结 

 

在本指南中,我们涵盖了从简单同步代码到高级异步原语的 JavaScript 错误处理完整概念。

在我们的 JavaScript 程序中,可以通过多种方式来显示异常。

同步代码中的异常是最容易捕获的。相反,异步代码 路径中的 异常 可能很难处理。

同时,浏览器中的新 JavaScript API 几乎都通向 Promise。这种普遍的模式使我们更容易用 then/catch/finally 或 try/catch 对 async/await 处理异常。

阅读本指南后,你应该能够 识别程序中可能出现的所有不同情况,并正确捕获 异常。

分类
已于2021-4-6 09:41:17修改
收藏
回复
举报
回复
    相关推荐