当前位置:   article > 正文

关于JavaScript错误处理最完整的指南

javascript reason:can not found result.dat file

作者丨VALENTINO GAGLIARDI

译者丨王强

策划丨小智

来自公众号:前端之巅

本文将介绍如何处理同步和异步 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”:

  1. const wrongType = TypeError("Wrong type given, expected number");
  2. wrongType.message; // "Wrong type given, expected number"
  3. wrongType.name; // "TypeError"

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

JavaScript 中的错误类型

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

  • Error

  • EvalError

  • InternalError

  • RangeError

  • ReferenceError

  • SyntaxError

  • TypeError

  • URIError

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

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

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

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

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

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

  1. va x = '33';
  2. // SyntaxError: Unexpected identifier

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

  1. function wrong(){
  2.     await 99;
  3. }
  4. wrong();
  5. // 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 上的这一页面:

https://developer.mozilla.org/en-US/docs/Web/API/DOMException

什么是异常?

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

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

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

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

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

或:

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

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

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

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

  1. throw Symbol();
  2. throw 33;
  3. throw "Error!";
  4. throw null;

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

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

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

考虑以下代码:

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

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

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

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

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

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

我们可以说:

  • 第 9 行中的代码调用了 toUppercase

  • toUppercase 在第 3 行爆炸了

除了在浏览器的控制台中看到这个堆栈跟踪外,你还可以在错误对象的 stack 属性上访问它。

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

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

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

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

同步错误处理

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

常规函数的错误处理

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

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

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

  1. try {
  2.   toUppercase(4);
  3. } catch (error) {
  4.   console.error(error.message);
  5.   // or log remotely
  6. } finally {
  7.   // clean up
  8. }

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

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

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

生成器函数的错误处理

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

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

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

  1. function* generate() {
  2. //
  3. }

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

  1. function* generate() {
  2.   yield 33;
  3.   yield 99;
  4. }

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

  • 在迭代器对象上调用 next()。

  • for...of 的 迭代

以我们的示例为例,要从生成器获取值,我们可以这样做:

  1. function* generate() {
  2.   yield 33;
  3.   yield 99;
  4. }
  5. const go = generate();

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

  1. function* generate() {
  2.   yield 33;
  3.   yield 99;
  4. }
  5. const go = generate();
  6. const firstStep = go.next().value; // 33
  7. const secondStep = go.next().value; // 99

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

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

  1. function* generate() {
  2.   yield 33;
  3.   yield 99;
  4. }
  5. const go = generate();
  6. const firstStep = go.next().value; // 33
  7. go.throw(Error("Tired of iterating!"));
  8. const secondStep = go.next().value; // never reached

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

  1. function* generate() {
  2.   try {
  3.     yield 33;
  4.     yield 99;
  5.   } catch (error) {
  6.     console.error(error.message);
  7.   }
  8. }

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

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

  1. function* generate() {
  2.   yield 33;
  3.   yield 99;
  4.   throw Error("Tired of iterating!");
  5. }
  6. try {
  7.   for (const value of generate()) {
  8.     console.log(value);
  9.   }
  10. } catch (error) {
  11.   console.error(error.message);
  12. }
  13. /* Output:
  14. 33
  15. 99
  16. Tired of iterating!
  17. */

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

异步错误处理

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

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

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

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

我们来看一些例子。

计时器错误处理

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

考虑以下代码段:

  1. function failAfterOneSecond() {
  2.   setTimeout(() => {
  3.     throw Error("Something went wrong!");
  4.   }, 1000);
  5. }

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

  1. function failAfterOneSecond() {
  2.   setTimeout(() => {
  3.     throw Error("Something went wrong!");
  4.   }, 1000);
  5. }
  6. try {
  7.   failAfterOneSecond();
  8. } catch (error) {
  9.   console.error(error.message);
  10. }

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

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

  1. Track A: --> try/catch
  2. 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 的模式

考虑以下示例:

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

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

  1. const button = document.querySelector("button");
  2. try {
  3.   button.addEventListener("click", function() {
  4.     throw Error("Can't touch this button!");
  5.   });
  6. } catch (error) {
  7.   console.error(error.message);
  8. }

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

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

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

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

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

onerror 是什么情况?

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

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

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

考虑以下示例:

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

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

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

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

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

更好的是:

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

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

使用 Promise 处理错误

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

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

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

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

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

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

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

代码会记录:

Wrong type given, expected a string

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

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

  1. toUppercase(99)
  2.   .then(result => result)
  3.   .catch(error => console.error(error.message))
  4.   .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 链。

考虑以下示例:

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

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

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

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

  1. fetch("https://example-dev/api/")
  2.   .then(response => {
  3.     if (!response.ok) {
  4.       throw Error(response.statusText);
  5.     }
  6.     return response.json();
  7.   })
  8.   .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 化”计时器的错误处理

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

  1. function failAfterOneSecond() {
  2.   setTimeout(() => {
  3.     throw Error("Something went wrong!");
  4.   }, 1000);
  5. }
  6. // DOES NOT WORK
  7. try {
  8.   failAfterOneSecond();
  9. } catch (error) {
  10.   console.error(error.message);
  11. }

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

  1. function failAfterOneSecond() {
  2.   return new Promise((_, reject) => {
  3.     setTimeout(() => {
  4.       reject(Error("Something went wrong!"));
  5.     }, 1000);
  6.   });
  7. }

通过 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 返回一个结果数组:

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

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

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

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

  1. Promise.all([promise1, promise2, promise3])
  2.   .then(results => console.log(results))
  3.   .catch(error => console.error(error.message))
  4.   .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。考虑以下示例:

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

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

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

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

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

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

  1. //
  2.   .catch(error => console.error(error.errors))
  3. //

Promise.race 中的错误处理

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

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

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

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

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

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

Promise.allSettled 中的错误处理

Promise.allSettled 是 ECMAScript 2020 加入的。

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

考虑以下示例:

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

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

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

  1. [
  2.   { status: 'fulfilled', value: 'Good!' },
  3.   {
  4.     status: 'rejected',
  5.     reason: Error: No good, sorry!
  6.   }
  7. ]

async/await 的错误处理

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

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

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

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

  1. async function toUppercase(string) {
  2.   if (typeof string !== "string") {
  3.     throw TypeError("Wrong type given, expected a string");
  4.   }
  5.   return string.toUpperCase();
  6. }
  7. toUppercase("abc")
  8.   .then(result => console.log(result))
  9.   .catch(error => console.error(error.message))
  10.   .finally(() => console.log("Always runs!"));

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

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

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

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

  1. async function toUppercase(string) {
  2.   if (typeof string !== "string") {
  3.     throw TypeError("Wrong type given, expected a string");
  4.   }
  5.   return string.toUpperCase();
  6. }
  7. async function consumer() {
  8.   try {
  9.     await toUppercase(98);
  10.   } catch (error) {
  11.     console.error(error.message);
  12.   } finally {
  13.     console.log("Always runs!");
  14.   }
  15. }
  16. consumer(); // Returning Promise ignored

输出是:

  1. Wrong type given, expected a string
  2. Always runs!

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

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

异步生成器的错误处理

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

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

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

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

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

  • then 处理器。

  • async 迭代

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

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

代码输出是:

  1. { value: 33, done: false }
  2. { value: 99, done: false }
  3. Something went wrong!

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

下面是完整的示例:

  1. async function* asyncGenerator() {
  2.   yield 33;
  3.   yield 99;
  4.   throw Error("Something went wrong!"); // Promise.reject
  5. }
  6. async function consumer() {
  7.   for await (const value of asyncGenerator()) {
  8.     console.log(value);
  9.   }
  10. }
  11. consumer();

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

  1. async function* asyncGenerator() {
  2.   yield 33;
  3.   yield 99;
  4.   throw Error("Something went wrong!"); // Promise.reject
  5. }
  6. async function consumer() {
  7.   try {
  8.     for await (const value of asyncGenerator()) {
  9.       console.log(value);
  10.     }
  11.   } catch (error) {
  12.     console.error(error.message);
  13.   }
  14. }
  15. consumer();

代码输出是:

  1. 33
  2. 99
  3. Something went wrong!

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

  1. async function* asyncGenerator() {
  2.   yield 33;
  3.   yield 99;
  4.   yield 11;
  5. }
  6. const go = asyncGenerator();
  7. go.next().then(value => console.log(value));
  8. go.next().then(value => console.log(value));
  9. go.throw(Error("Let's reject!"));
  10. go.next().then(value => console.log(value)); // value is undefined

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

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

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

  1. async function* asyncGenerator() {
  2.   try {
  3.     yield 33;
  4.     yield 99;
  5.     yield 11;
  6.   } catch (error) {
  7.     console.error(error.message);
  8.   }
  9. }
  10. const go = asyncGenerator();
  11. go.next().then(value => console.log(value));
  12. go.next().then(value => console.log(value));
  13. go.throw(Error("Let's reject!"));
  14. go.next().then(value => console.log(value)); // value is undefined

Node.js 中的错误处理

Node.js 中的同步错误处理

Node.js 中的同步错误处理与上文介绍的内容并没有太大差异。

对于 同步代码,try/catch/finally 没什么问题。

但如果我们进入异步世界,事情就会变得很有趣了。

Node.js 中的异步错误处理:回调模式

对于异步代码,Node.js 强烈依赖两个习惯用法:

  • 回调模式。

  • 事件发射器。

回调模式 中,异步 Node.jsAPI 接收一个函数,该函数通过 事件循环 处理,并在 调用堆栈 为空时立即执行。

考虑以下代码:

  1. const { readFile } = require("fs");
  2. function readDataset(path) {
  3.   readFile(path, { encoding: "utf8" }, function(error, data) {
  4.     if (error) console.error(error);
  5.     // do stuff with the data
  6.   });
  7. }

如果从此清单中提取回调,就可以看到错误的处理方式:

  1. //
  2. function(error, data) {
  3.     if (error) console.error(error);
  4.     // do stuff with the data
  5.   }
  6. //

如果使用 fs.readFile 读取给定路径时引发任何错误,我们将获得一个错误对象。这时我们可以:

  • 像之前一样简单地记录错误对象。

  • 抛出一个异常。

  • 将这个错误传递给另一个回调。

要抛出异常,我们可以执行以下操作:

  1. const { readFile } = require("fs");
  2. function readDataset(path) {
  3.   readFile(path, { encoding: "utf8" }, function(error, data) {
  4.     if (error) throw Error(error.message);
  5.     // do stuff with the data
  6.   });
  7. }

但是,与 DOM 中的事件和计时器一样,此异常将 使程序崩溃。尝试使用 try/catch 停止它的方法将不起作用:

  1. const { readFile } = require("fs");
  2. function readDataset(path) {
  3.   readFile(path, { encoding: "utf8" }, function(error, data) {
  4.     if (error) throw Error(error.message);
  5.     // do stuff with the data
  6.   });
  7. }
  8. try {
  9.   readDataset("not-here.txt");
  10. } catch (error) {
  11.   console.error(error.message);
  12. }

如果我们不想使程序崩溃,则将错误传递给另一个回调是首选方法

  1. const { readFile } = require("fs");
  2. function readDataset(path) {
  3.   readFile(path, { encoding: "utf8" }, function(error, data) {
  4.     if (errorreturn errorHandler(error);
  5.     // do stuff with the data
  6.   });
  7. }

顾名思义,errorHandler 是一个用于错误处理的简单函数:

  1. function errorHandler(error) {
  2.   console.error(error.message);
  3.   // do something with the error:
  4.   // - write to a log.
  5.   // - send to an external logger.
  6. }

Node.js 中的异步错误处理:事件发射器

你在 Node.js 中所做的大部分工作都是基于 事件 的。大多数情况下,你会与 发射器对象 和一些观察者交互以侦听消息。

Node.js 中的任何事件驱动模块(例如 net,https://nodejs.org/dist/latest-v14.x/docs/api/net.html)都扩展了一个名为 EventEmitter 的根类。

Node.js 中的 EventEmitter 有两种基本方法:on 和 emit。

考虑以下简单的 HTTP 服务器:

  1. const net = require("net");
  2. const server = net.createServer().listen(8081"127.0.0.1");
  3. server.on("listening", function () {
  4.   console.log("Server listening!");
  5. });
  6. server.on("connection", function (socket) {
  7.   console.log("Client connected!");
  8.   socket.end("Hello client!");
  9. });

在这里我们监听两个事件:listening 和 connection。除了这些事件之外,事件发射器还在出现错误时公开一个 错误 事件。

如果你在端口 80 上运行此代码,则会得到一个异常:

  1. const net = require("net");
  2. const server = net.createServer().listen(80"127.0.0.1");
  3. server.on("listening", function () {
  4.   console.log("Server listening!");
  5. });
  6. server.on("connection", function (socket) {
  7.   console.log("Client connected!");
  8.   socket.end("Hello client!");
  9. });

输出:

  1. events.js:291
  2.       throw er; // Unhandled 'error' event
  3.       ^
  4. Error: listen EACCES: permission denied 127.0.0.1:80
  5. Emitted 'error' event on Server instance at: ...

要捕获它,我们可以注册一个 错误 事件处理器:

  1. server.on("error", function(error) {
  2.   console.error(error.message);
  3. });

这会打印:

listen EACCES: permission denied 127.0.0.1:80

此外,该程序不会崩溃。要了解有关该主题的更多信息,请参考“Node.js 中的错误处理”。

https://www.joyent.com/node-js/production/design/errors

    总结    

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

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

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

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

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

感谢你的阅读和关注!

 延伸阅读

https://www.valentinog.com/blog/error/

相关文章:

有用点个在看吧!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/320971
推荐阅读
相关标签
  

闽ICP备14008679号