前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 错误处理大全【建议收藏】

JavaScript 错误处理大全【建议收藏】

作者头像
疯狂的技术宅
发布2020-09-01 11:09:57
6.3K0
发布2020-09-01 11:09:57
举报
文章被收录于专栏:京程一灯
代码语言:javascript
复制
// 每日前端夜话 第391篇
// 正文共:6800 字
// 预计阅读时间:17 分钟

❝目录

  • 编程中有什么错误?
  • JavaScript 中有什么错误?
  • JavaScript 中的错误类型
  • 什么是异常?
  • 当抛出异常时会发生什么?
  • 同步错误处理
    • 常规函数的错误处理
    • 生成器函数的错误处理
  • 异步错误处理
    • 计时器错误处理
    • 事件的错误处理
    • How about onerror?
    • 怎么处理 onerror?
    • 用 Promise 处理错误
    • Promise, error 和 throw
    • 错误处理 “promisified” 计时器
    • Promise.all 中的错误处理
    • Promise.any 中的错误处理
    • Promise.race 中的错误处理
    • Promise.allSettled 中的错误处理
    • async/await 的错误处理
    • 异步生成器的错误处理
  • Node.js中的错误处理
    • Node.js 中的同步错误处理
    • Node.js 中的异步错误处理:回调模式
    • Node.js 中的异步错误处理:事件发射器
  • 总结

编程中有什么错误?

在我们的程序中,事情并非一帆风顺

特别是在某些情况下,我们可能希望在停止程序或在发生不良状况时通知用户。例如:

  • 程序试图打开一个不存在的文件。
  • 网络连接断开。
  • 用户进行了无效的输入。

在所有的这些情况下,我们作为程序员都会产生错误,或者让编程引擎为我们创建一些错误。

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

JavaScript 中有什么错误?

JavaScript 中的错误是一个对象,随后被抛出,用以终止程序。

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

代码语言:javascript
复制
const err = new Error("Something bad happened!");

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

代码语言:javascript
复制
const err = Error("Something bad happened!");

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

  • message:带有错误信息的字符串。
  • name:错误的类型。
  • stack:函数执行的栈跟踪。

例如,如果我们用适当的消息创建一个新的 TypeError 对象,则 message 将携带实际的错误字符串,而 name 则为 TypeError

代码语言:javascript
复制
const wrongType = TypeError("Wrong type given, expected number");

wrongType.message; // "Wrong type given, expected number"
wrongType.name; // "TypeError"

Firefox 还实现了一堆非标准属性,例如 columnNumberfilenamelineNumber

JavaScript 中的错误类型

JavaScript 中有很多类型的错误,即:

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

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

在代码中主要用 ErrorTypeError 这两种最常见的类型来创建自己的错误对象。

但是在大多数情况下,很多错误直接来自 JavaScript 引擎,例如 InternalErrorSyntaxError

下面的例子是当你尝试重新为 const 赋值时,将触发 TypeError

代码语言:javascript
复制
const name = "Jules";
name = "Caty";

// TypeError: Assignment to constant variable.

当你关键字拼错时,就会触发 SyntaxError

代码语言:javascript
复制
va x = '33';
// SyntaxError: Unexpected identifier

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

代码语言:javascript
复制
function wrong(){
    await 99;
}

wrong();

// SyntaxError: await is only valid in async function

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

代码语言:javascript
复制
Uncaught TypeError: button is null

除了这些“传统的”错误对象外,AggregateError 对象也即将能够在 JavaScript 中使用。

AggregateError 可以把多个错误很方便地包装在一起,在后面将会看到。

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

  • DOMException
  • DOMError 已弃用,目前不再使用。

DOMException 是与 Web API 相关的一系列错误。有关完整列表,请参见 MDN。

什么是异常?

很多人认为错误和异常是一回事。实际上错误对象仅在抛出时才成为异常

要在 JavaScript 中引发异常,我们使用 throw 关键字,后面跟错误对象:

代码语言:javascript
复制
const wrongType = TypeError("Wrong type given, expected number");

throw wrongType;

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

代码语言:javascript
复制
throw TypeError("Wrong type given, expected number");

或者:

代码语言:javascript
复制
throw new TypeError("Wrong type given, expected number");

一般不会把异常抛出到函数或条件块之外,当然也有例外情况,例如:

代码语言:javascript
复制
function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

在代码中我们检查函数的参数是否为字符串,如果不是则抛出异常。

从技术上讲,你可以在 JavaScript 中抛出任何东西,而不仅仅是错误对象:

代码语言:javascript
复制
throw Symbol();
throw 33;
throw "Error!";
throw null;

但是,最好不要这样做,应该总是抛出正确的错误对象,而不是原始类型

这样就可以通过代码库保持错误处理的一致性。其他团队成员总是能够在错误对象上访问 error.messageerror.stack

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

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

看下面的代码:

代码语言:javascript
复制
function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

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

代码语言:javascript
复制
Uncaught TypeError: Wrong type given, expected a string
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

另外还可以看到发生错误的代码行数。

这段报告是 栈跟踪(stack trace),对于跟踪代码中的问题很有帮助。

栈跟踪从底部到顶部。所以是这样的:

代码语言:javascript
复制
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

我们可以说:

  • 程序的第 9 行中名为 toUppercase 的内容
  • toUppercase 在第 3 行引发了一个问题

除了在浏览器的控制台中看到栈跟踪之外,还可以在错误对象的 stack 属性上对其进行访问。

如果异常是未捕获的,也就是说程序员没有采取任何措施来捕获它,则程序将会崩溃。

你在什么时候及在什么地方捕获代码中的异常取决于特定的用例

例如,你可能想要在栈中传播异常,使程序完全崩溃。当发生致命的错误,需要更安全地停止程序而不是处理无效数据时,你可能需要这样做。

介绍了基础知识之后,现在让我们将注意力转向同步和异步 JavaScript 代码中的错误和异常处理

同步错误处理

同步代码通常很简单,它的错误处理也是如此。

常规函数的错误处理

同步代码按照代码顺序按部就班的执行。让我们再来看前面的例子:

代码语言: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

代码语言:javascript
复制
try {
  toUppercase(4);
} catch (error) {
  console.error(error.message);
  // or log remotely
} finally {
  // clean up
}

通常try 处理主处理流程或者可能引发异常的函数调用。

catch 则捕获实际的异常。它接收错误对象,可以在这里对其进行检查(并远程发送到生产环境中的日志服务器)。

另外无论函数的执行结果如何,不管是成功还是失败,finally 中的所有代码都会被执行。

请记住:try/catch/finally 是一个同步结构:它可以捕获来自异步代码的异常

生成器函数的错误处理

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

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

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

代码语言:javascript
复制
function* generate() {
 //
}

进入函数后,可以使用 yield 返回值:

代码语言:javascript
复制
function* generate() {
  yield 33;
  yield 99;
}

生成器函数的返回值是迭代器对象。有两种方法从生成器中提取值

  • 在迭代器对象上调用 next()
  • iteration with for...of.
  • 带有 for ... of 的迭代。

以上面的代码为例,要从生成器获取值,可以这样做:

代码语言:javascript
复制
function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

当调用生成器函数时,go 成了我们的迭代器对象。

现在可以调用go.nex() 来执行:

代码语言:javascript
复制
function* generate() {
  yield 33;
  yield 99;
}

const go = generate();

const firstStep = go.next().value; // 33
const secondStep = go.next().value; // 99

生成器也可以通过其他方式工作:它们可以接受调用者返回的值和异常

除了 next() 外,从生成器返回的迭代器对象还有 throw() 方法。用这个方法,可以通过把异常注入到生成器来暂停程序:

代码语言:javascript
复制
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,如果需要的话)将代码包装在生成器中来捕获这样的错误:

代码语言:javascript
复制
function* generate() {
  try {
    yield 33;
    yield 99;
  } catch (error) {
    console.error(error.message);
  }
}

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

下面是通过 for ... of 从外部使用的生成器函数的例子:

代码语言:javascript
复制
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 块内的主处理流程。如果发生任何异常,就用 catch 停止。

异步错误处理

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

诸如浏览器引擎之类的环境用许多 Web API 增强了 JavaScript,用来与外部系统进行交互并处理与 I/O 绑定的操作。

浏览器中的异步示例包括timeouts、events、Promise

异步代码中的错误处理与同步代码不同。

看一些例子:

计时器错误处理

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

思考下面的代码段:

代码语言:javascript
复制
function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

这个函数将在大约 1 秒钟后被触发。那么处理这个异常的正确方式是什么?

下面的例子是无效的

代码语言:javascript
复制
function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

正如前面所说的,try/catch 是同步的。另一方面,我们有 setTimeout,这是一个用于定时器的浏览器 API。

到传递给 setTimeout 的回调运行时,try/catch 已经“消失了”。程序将会崩溃,因为我们无法捕获异常。

它们在两条不同的轨道上行驶

代码语言:javascript
复制
Track A: --> try/catch
Track B: --> setTimeout --> callback --> throw

如果我们不想使程序崩溃,为了正确处理错误,我们必须把 try/catch 移动到 setTimeout 的回调中。

但是这在大多数情况下并没有什么意义。Promises 的异步错误处理提供了更好的方式

事件的错误处理

文档对象模型中的HTML节点连接到 EventTargetEventTarget 是浏览器中所有 event emitter 的共同祖先。

这意味着我们可以侦听页面中任何 HTML 元素上的事件。Node.js 将在未来版本中支持 EventTarget

DOM 事件的错误处理机制遵循与异步 Web API 的相同方案。

看下面的例子:

代码语言:javascript
复制
const button = document.querySelector("button");

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

在这里,单击按钮后会立即引发异常,应该怎样捕获它?下面的方法不起作用,而且不会阻止程序崩溃:

代码语言:javascript
复制
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 的任何回调均异步执行:

代码语言:javascript
复制
Track A: --> try/catch
Track B: --> addEventListener --> callback --> throw

如果不想使程序崩溃,为了正确处理错误,必须把 try/catch 放在 addEventListener 的回调内。但这样做没有任何价值。与 setTimeout 一样,异步代码路径引发的异常从外部是无法捕获的,这将会使程序崩溃。

How about onerror?

怎么处理 onerror?

HTML 元素具有许多事件处理函数,例如 onclickonmouseenteronchange 等。

还有 onerror,但是它与 throw 没有什么关系。

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

看下面的例子:

代码语言:javascript
复制
// omitted
<body>
 <img src="nowhere-to-be-found.png" alt="So empty!">
</body>
// omitted

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

代码语言:javascript
复制
GET http://localhost:5000/nowhere-to-be-found.png
[HTTP/1.1 404 Not Found 3ms]

在 JavaScript 中,我们有机会使用适当的事件处理程序来“捕获”这个错误:

代码语言:javascript
复制
const image = document.querySelector("img");

image.onerror = function(event) {
  console.log(event);
};

或者用更好的方法:

代码语言:javascript
复制
const image = document.querySelector("img");

image.addEventListener("error", function(event) {
  console.log(event);
});

此模式可用于加载替代资源来替换丢失的图像或脚本

但是要记住:onerrorthrowtry/catch 无关。

用 Promise 处理错误

为了说明 Promise 的错误处理,我们将 “Promise” 前面的一个例子。调整以下功能:

代码语言:javascript
复制
function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

为了代替返回简单的字符串或异常,可以分别用 Promise.rejectPromise.resolve 处理错误和成功:

代码语言:javascript
复制
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

代码语言:javascript
复制
toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message));

这段代码将会输出:

代码语言:javascript
复制
Wrong type given, expected a string

在 Promise 领域,catch 是用于处理错误的结构。

除了 catchthen 之外,还有 finally,类似于 try/catch 中的 finally

相对于同步而言,Promise 的 finally 运行与 Promise 结果无关

代码语言:javascript
复制
toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message))
  .finally(() => console.log("Run baby, run"));

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

Promise, error 和 throw

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

代码语言:javascript
复制
Promise.reject(TypeError("Wrong type given, expected a string"));

这样,你可以通过代码库保持错误处理的一致性。其他团队成员总是可以期望访问 error.message,更重要的是你可以检查栈跟踪。

除了 Promise.reject 之外,可以通过抛出异常来退出 Promise 链。

看下面的例子:

代码语言:javascript
复制
Promise.resolve("A string").then(value => {
  if (typeof value === "string") {
    throw TypeError("Expected a number!");
  }
});

我们用一个字符串解决一个 Promise,然后立即用 throw 打破这个链。

为了阻止异常的传播,照常使用 catch

代码语言:javascript
复制
Promise.resolve("A string")
  .then(value => {
    if (typeof value === "string") {
      throw TypeError("Expected a number!");
    }
  })
  .catch(reason => console.log(reason.message));

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

代码语言:javascript
复制
fetch("https://example-dev/api/")
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }

    return response.json();
  })
  .then(json => console.log(json));

在这里可以用 catch 拦截异常。如果失败了,或者决定不去捕获它,则异常可以在栈中冒泡

从本质上讲,这还不错,但是在不同的环境下对未捕获的 rejection 的反应不同。

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

代码语言:javascript
复制
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.

更好地捕获他们!

错误处理 “promisified” 计时器

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

代码语言:javascript
复制
function failAfterOneSecond() {
  setTimeout(() => {
    throw Error("Something went wrong!");
  }, 1000);
}

// DOES NOT WORK
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

Promise 提供的解决方案在于代码的“promisification”。基本上,我们用 Promise 包装计时器:

代码语言:javascript
复制
function failAfterOneSecond() {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(Error("Something went wrong!"));
    }, 1000);
  });
}

通过 reject,我们启动了Promise rejection,它带有一个错误对象。

这时可以用 catch 处理异常:

代码语言:javascript
复制
failAfterOneSecond().catch(reason => console.error(reason.message));

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

Node.js 有一个名为promisify的工具函数,可以简化旧式回调 API 的“混杂”。

Promise.all 中的错误处理

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

代码语言:javascript
复制
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,就像在上一节中所做的那样:

代码语言:javascript
复制
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

代码语言:javascript
复制
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。考虑以下示例:

代码语言:javascript
复制
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!"));

这里用 catch 处理错误。这里代码的输出是:

代码语言:javascript
复制
AggregateError: No Promise in Promise.any was resolved
Always runs!

AggregateError 对象有与基本 Error 相同的属性,以及 Errors 属性:

代码语言:javascript
复制
//
  .catch(error => console.error(error.errors))
//

这个属性是拒绝产生的每个错误的数组:

代码语言:javascript
复制
[Error: "No good, sorry!, Error: "Bad day ..."]

Promise.race 中的错误处理

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

代码语言:javascript
复制
const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, promise2]).then(result => console.log(result));

// The first!

结果是第一个赢得“race”的 Promise

那 rejection 呢?如果拒绝的 Promise 不是第一个出现在输入数组中的对象,则 Promise.race 解析:

代码语言:javascript
复制
const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, rejection, promise2]).then(result =>
  console.log(result)
);

// The first!

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

代码语言:javascript
复制
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 **。

看下面的例子:

代码语言:javascript
复制
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 组成的数组传递给 Promise.allSettled:一个已解决,另一个被拒绝。

在这种情况下,catch 将永远不会被执行。finally 会运行。

日志输出的 then 的代码的结果是:

代码语言:javascript
复制
[
  { status: 'fulfilled', value: 'Good!' },
  {
    status: 'rejected',
    reason: Error: No good, sorry!
  }
]

async/await 的错误处理

JavaScript 中的 await 表示异步函数,但从维护者的角度来看,它们受益于同步函数的所有“可读性”。

为了简单起见,我们将使用先前的同步函数 toUppercase,并将 async 放在 function 关键字之前,将其转换为异步函数:

代码语言:javascript
复制
async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

只需在函数前面加上 async,就可以使函数返回一个Promise。这意味着我们可以在函数调用之后链接 thencatchfinally

代码语言:javascript
复制
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!"));

当我们从异步函数中抛出异常时,异常会成为导致底层 Promise 被拒绝的原因。

任何错误都可以通过外部的 catch 来拦截。

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

在下面的例子中,我们从另一个函数 consumer 调用 toUppercase,该函数用 try/catch/finally 方便地包装函数调用:

代码语言:javascript
复制
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

输出为:

代码语言:javascript
复制
Wrong type given, expected a string
Always runs!

异步生成器的错误处理

JavaScript 中的异步生成器(Async generators) 不是生产简单值,而是能够生成 Promise 的生成器函数 。

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

我们用前缀为 async 和星号 * 声明一个异步生成器函数。

代码语言:javascript
复制
async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // Promise.reject
}

基于 Promise 用于错误处理的相同规则,异步生成器中的 throw 导致 Promise 拒绝,用 catch 进行拦截。

有两种方法可以把 Promise 拉出异步生成器

  • then
  • 异步迭代

从上面的例子中,在前两个 yield 之后会有一个例外。这意味着我们可以做到:

代码语言:javascript
复制
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));

这段代码的输出是:

代码语言:javascript
复制
{ value: 33, done: false }
{ value: 99, done: false }
Something went wrong!

另一种方法是使用异步迭代for await...of。要使用异步迭代,需要用 async 函数包装使用者。

这是完整的例子:

代码语言:javascript
复制
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 处理任何潜在的异常:

代码语言:javascript
复制
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();

这段代码的输出是:

代码语言:javascript
复制
33
99
Something went wrong!

从异步生成器函数返回的迭代器对象也有一个 throw() 方法,非常类似于它的同步对象。

在这里的迭代器对象上调用 throw() 不会引发异常,但是会被 Promise 拒绝:

代码语言:javascript
复制
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

可以通过执行以下操作从外部处理这种情况:

代码语言:javascript
复制
go.throw(Error("Let's reject!")).catch(reason => console.error(reason.message));

但是,别忘了迭代器对象 throw() 在生成器内部发送异常。这意味着我们还可以用以下模式:

代码语言:javascript
复制
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

Node.js中的错误处理

Node.js 中的同步错误处理

Node.js 中的同步错误处理与到目前为止所看到的并没有太大差异。

对于同步代码try/catch/finally 可以正常工作。

但是如果进入异步世界,事情就会变得有趣。

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

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

  • 回调模式。
  • 事件发射器(event emitter)。

在回调模式中,异步 Node.js API 接受通过事件循环处理的函数,并在调用栈为空时立即执行。

看下面的代码:

代码语言:javascript
复制
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) console.error(error);
    // 处理数据
  });
}

如果从这个清单中提取回调,则可以看到应该如何处理错误:

代码语言:javascript
复制
//
function(error, data) {
    if (error) console.error(error);
    // 处理数据
  }
//

如果通过使用 fs.readFile 读取给定路径而引起任何错误,将得到一个错误对象。

在这一点上,我们可以:

  • 简单地把错误对象输出到日志。
  • 引发异常。
  • 将错误传递给另一个回调。

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

代码语言:javascript
复制
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

但是,与 DOM 中的事件和计时器一样,这个异常将会使程序崩溃。下面的代码尝试通过 try/catch 的处理将不起作用:

代码语言:javascript
复制
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

try {
  readDataset("not-here.txt");
} catch (error) {
  console.error(error.message);
}

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

代码语言:javascript
复制
const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) return errorHandler(error);
    // do stuff with the data
  });
}

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

代码语言:javascript
复制
function errorHandler(error) {
  console.error(error.message);
  // do something with the error:
  // - write to a log.
  // - send to an external logger.
}

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

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

Node.js 中的任何事件驱动模块(例如net)都会扩展名为 EventEmitter 的根类 。

Node.js中的 EventEmitter 有两种基本方法:onemit

看下面这个简单的 HTTP 服务器:

代码语言:javascript
复制
const net = require("net");

const server = net.createServer().listen(8081, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});

在这里,我们监听两个事件:listeningconnection

除了这些事件之外,事件发射器还暴露了 error 事件,以防发生错误。

在 80 端口上运行代码,会得到一个异常:

代码语言:javascript
复制
const net = require("net");

const server = net.createServer().listen(80, "127.0.0.1");

server.on("listening", function () {
  console.log("Server listening!");
});

server.on("connection", function (socket) {
  console.log("Client connected!");
  socket.end("Hello client!");
});

输出:

代码语言:javascript
复制
events.js:291
      throw er; // Unhandled 'error' event
      ^

Error: listen EACCES: permission denied 127.0.0.1:80
Emitted 'error' event on Server instance at: ...

要捕获它,可以为 error 注册一个事件处理函数:

代码语言:javascript
复制
server.on("error", function(error) {
  console.error(error.message);
});

这将会输出:

代码语言:javascript
复制
listen EACCES: permission denied 127.0.0.1:80

并且程序不会崩溃。

总结

在本文中,我们介绍了从简单的同步代码到高级异步原语,以及整个 JavaScript 的错误处理

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

同步代码中的异常是最容易捕获的。而来自异步代码路径的异常处理可能会有些棘手。

同时,浏览器中的新 JavaScript API 几乎都朝着 Promise 的方向发展。这种普遍的模式使得用 then/catch/finally 或用 try/catch 来处理 async/await 异常更加容易。

看完本文后,你应该能够识别程序中可能会出现的所有不同情况,并正确捕获异常。


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端先锋 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 编程中有什么错误?
  • JavaScript 中有什么错误?
  • JavaScript 中的错误类型
  • 什么是异常?
  • 当抛出异常时会发生什么?
  • 同步错误处理
    • 常规函数的错误处理
      • 生成器函数的错误处理
      • 异步错误处理
        • 计时器错误处理
          • 事件的错误处理
            • How about onerror?
              • 怎么处理 onerror?
                • 用 Promise 处理错误
                  • Promise, error 和 throw
                    • 错误处理 “promisified” 计时器
                      • Promise.all 中的错误处理
                        • Promise.any 中的错误处理
                          • Promise.race 中的错误处理
                            • Promise.allSettled 中的错误处理
                              • async/await 的错误处理
                                • 异步生成器的错误处理
                                • Node.js中的错误处理
                                  • Node.js 中的同步错误处理
                                    • Node.js 中的异步错误处理:回调模式
                                      • Node.js 中的异步错误处理:事件发射器
                                      • 总结
                                      相关产品与服务
                                      日志服务
                                      日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档