也使用异步迭代 for await...of。 要使用异步迭代,需要用 async 函数包装 consumer:
async function* asyncGenerator() {
yield 33;
yield 99;
throw Error("Bad"); // 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("Bad"); // Promise.reject
}
async function consumer() {
try {
for await (const value of asyncGenerator()) {
console.log(value);
}
} catch (error) {
console.error(error.message);
}
}
consumer();
输出结果如下:
从异步生成器函数返回的迭代器对象也有一个 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("Reject!"));
go.next().then(value => console.log(value));
输出结果如下:
可以通过以下方式来捕获错误:
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("Reject!"));
go.next().then(value => console.log(value));
5. Node.js 错误处理(1)同步错误处理
Node.js 中的同步错误处理与 JavaScript 是一样的,可以使用 try/catch/finally。
(2)异步错误处理:回调模式对于异步代码,Node.js 强烈依赖两个术语:
- 事件发射器
- 回调模式
在回调模式中,异步 Node.js API 接受一个函数,该函数通过事件循环处理并在调用堆栈为空时立即执行。
来看下面的例子:
const { readFile } = require("fs");
function readDataset(path) {
readFile(path, { encoding: "utf8" }, function(error, data) {
if (error) console.error(error);
// data操作
});
}
这里可以看到回调中错误处理:
function(error, data) {
if (error) console.error(error);
// data操作
}
如果使用 fs.readFile 读取给定路径时出现任何错误,我们都会得到一个 error 对象。这时我们可以:
- 单地记录错误对象。
- 抛出异常。
- 将错误传递给另一个回调。
要想抛出异常,可以这样做:
const { readFile } = require("fs");
function readDataset(path) {
readFile(path, { encoding: "utf8" }, function(error, data) {
if (error) throw Error(error.message);
// data操作
});
}
但是,与 DOM 中的事件和计时器一样,这个异常会使程序崩溃。 使用 try/catch 停止它的尝试将不起作用:
const { readFile } = require("fs");
function readDataset(path) {
readFile(path, { encoding: "utf8" }, function(error, data) {
if (error) throw Error(error.message);
// data操作
});
}
try {
readDataset("not-here.txt");
} catch (error) {
console.error(error.message);
}
如果不想让程序崩溃,可以将错误传递给另一个回调:
const { readFile } = require("fs");
function readDataset(path) {
readFile(path, { encoding: "utf8" }, function(error, data) {
if (error) return errorHandler(error);
// data操作
});
}
这里的 errorHandler 是一个简单的错误处理函数:
function errorHandler(error) {
console.error(error.message);
// 处理错误:写入日志、发送到外部logger
}
(3)异步错误处理:事件发射器
Node.js 中的大部分工作都是基于事件的。大多数时候,我们会与发射器对象和一些侦听消息的观察者进行交互。
Node.js 中的任何事件驱动模块(例如 net)都扩展了一个名为 EventEmitter 的根类。EventEmitter 有两个基本方法:on 和 emit。
下面来看一个简单的 HTTP 服务器:
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!");
});
这里我们监听了两个事件:listening 和 connection。除了这些事件之外,事件发射器还公开一个错误事件,在出现错误时触发。
如果这段代码监听的端口是 80,就会得到一个异常:
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!");
});
输出结果如下:
events.js:291
throw er;
^
Error: listen EACCES: permission denied 127.0.0.1:80
Emitted 'error' event on Server instance at: ...
为了捕获它,可以为 error 注册一个事件处理函数:
server.on("error", function(error) {
console.error(error.message);
});
这样就会输出:
listen EACCES: permission denied 127.0.0.1:80
6. 错误处理最佳实践
最后,我们来看看处理 JavaScript 异常的最佳实践!
(1)不要过度处理错误错处理的第一个最佳实践就是不要过度使用“错误处理”。通常,我们会在外层处理错误,从内层抛出错误,这样一旦出现错误,就可以更好地理解是什么原因导致的。
然而,开发人员常犯的错误之一是过度使用错误处理。有时这样做是为了让代码在不同的文件和方法中看起来保持一致。但是,不幸的是,这些会对应用程序和错误检测造成不利影响。
因此,只关注代码中可能导致错误的地方,错误处理将有助于提高代码健壮性并增加检测到错误的机会。
(2)避免浏览器特定的非标准方法尽管许多浏览器都遵循一个通用标准,但某些特定于浏览器的 JavaScript 实现在其他浏览器上却失败了。例如,以下语法仅适用于 Firefox:
catch(e) {
console.error(e.filename ': ' e.lineNumber);
}
因此,在处理错误时,尽可能使用跨浏览器友好的 JavaScript 代码。
(3)远程错误记录当发生错误时,我们应该得到通知以了解出了什么问题。这就是错误日志的用武之地。JavaScript 代码是在用户的浏览器中执行的。因此,需要一种机制来跟踪客户端浏览器中的这些错误,并将它们发送到服务器进行分析。
可以尝试使用以下工具来监控并上报错误:
- Sentry(https://sentry.io/): 专注于异常(应用崩溃)而不是信息错误。它提供了应用中错误的完整概述,包括受影响的用户数量、调用堆栈、受影响的浏览器以及导致错误的提交等详细信息。
- Rollbar(https://rollbar.com/): 用于前端、后端和移动应用的无代理错误监控工具。它提供人工智能辅助的工作流程,使开发人员能够在错误影响用户之前立即采取行动。它会显示受错误影响的客户数量、受影响的平台或浏览器的类型以及之前是否发生过类似错误或是否已经存在解决方案等数据。
Node.js 环境支持使用中间件向服务端应用中添加功能。因此可以创建一个错误处理中间件。使用中间件的最大好处是所有错误都在一个地方集中处理。可以选择启用/禁用此设置以轻松进行测试。
以下是创建基本中间件的方法:
const logError = err => {
console.log("ERROR: " String(err))
}
const errorLoggerMiddleware = (err, req, res, next) => {
logError(err)
next(err)
}
const returnErrorMiddleware = (err, req, res, next) => {
res.status(err.statusCode || 500)
.send(err.message)
}
module.exports = {
logError,
errorLoggerMiddleware,
returnErrorMiddleware
}
可以像下面这样在应用中使用此中间件:
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')
app.use(errorLoggerMiddleware)
app.use(returnErrorMiddleware)
现在可以在中间件内定义自定义逻辑以适当地处理错误。而无需再担心在整个代码库中实现单独的错误处理结构。
(5)捕获所有未捕获的异常(Node.js)我们可能永远无法涵盖应用中可能发生的所有错误。因此,必须实施回退策略以捕获应用中所有未捕获的异常。
可以这样做:
process.on('uncaughtException', error => {
console.log("ERROR: " String(error))
// 其他处理机制
})
还可以确定发生的错误是标准错误还是自定义操作错误。根据结果,可以退出进程并重新启动它以避免意外行为。
(6)捕获所有未处理的 Promise 拒绝(Node.js)与异常不同的是,promise 拒绝不会抛出错误。因此,一个被拒绝的 promise 可能只是一个警告,这让应用有可能遇到意外行为。因此,实现处理 promise 拒绝的回退机制至关重要。
可以这样做:
const promiseRejectionCallback = error => {
console.log("PROMISE REJECTED: " String(error))
}
process.on('unhandledRejection', callback)
参考文章
- https://www.valentinog.com/blog/error/
- https://kinsta.com/blog/errors-in-javascript/
- https://blog.bitsrc.io/javascript-exception-handling-patterns-best-practices-f7d6fcab735d