Node.js 应用的御用品: Node.js 错误处理系统
开发中,有些开发者会积极寻求处理错误,力求减少开发时间,但也有些人完全忽略了错误的存在。正确处理错误不仅意味着能够轻松发现和纠正错误,而且还意味着能够为大型应用程序开发出稳健的代码库。
特别是对于 Node.js 开发人员,他们有时会也发现自己使用了不那么整洁的代码来处理各种错误,例如会在所有地方都用相同的逻辑来处理错误。那么,难道 Node.js 在处理错误方面不太友好 ?
不。本文里,我想告诉的是 Node.js 一点问题也没有。
Node.js 错误处理之错误类型
首先,我们有必要对 Node.js 中的错误有一个清晰的认识。一般来说,Node.js错误分为两大类: 操作错误 和 开发者错误。
- 操作错误:表示运行时问题,其结果是预期的,应该以适当的方式处理。操作错误并不意味着应用程序本身有错误,但开发者需要仔细处理它们。操作错误的例子包括“内存不足”、“API 参数的无效输入”等等。
- 开发者错误:是指在写得不好的代码中出现了意想不到的错误。意思就是代码逻辑本身有一些问题,需要解决。一个很好的例子是尝试读取
“undefined”
的属性。要解决这个问题,必须更改代码。因为这是开发者制造的错误,而不是操作错误。
接下来的一个问题是:“为什么我们要把它们分成两类来处理?”
原因是,如果你没有对错误有一个清晰的认识,那么每当出现错误时,你可能会想重启服务。而当成千上万的用户正在使用你的程序时,他们可能看到的是“Not Found”。那这样的重启是否有意义?
同样,如果你的代码逻辑发生错误的时候,给应用带来了意想不到的问题,影响到了用户体验,这是否有意义?
正确处理错误
假设你有一些使用异步 Js 的经验,那么在使用回调处理错误时可能会遇到一些挑战。例如在回调函数中你不断地进行错误检查,可能会导致嵌套过深,从而引发“回调地狱”的问题。这种情况会使代码流变得难以跟踪和理解。
那么,你可以使用 promise或async/await
替代回调。例如下面这段代码:
const doAsyncJobs = async () => {try {const result1 = await job1();const result2 = await job2(result1);const result3 = await job3(result2);return await job4(result3);} catch (error) {console.error(error);} finally {await anywayDoThisJob();}
}
在 Node.js 中有一个内置的 Error
对象,也是一个很好的处理办法,因为它包含了直观而清晰的错误信息,比如 StackTrace
,大多数开发者都依赖它来跟踪错误的根源。除此之外,还有一些其他有意义的属性,如 HTTP 状态码和通过扩展 Error 类的描述,将使其错误描述的更加具体。
class BaseError extends Error {public readonly name: string;public readonly httpCode: HttpStatusCode;public readonly isOperational: boolean;constructor(name: string, httpCode: HttpStatusCode, description: string, isOperational: boolean) {super(description);Object.setPrototypeOf(this, new.target.prototype);this.name = name;this.httpCode = httpCode;this.isOperational = isOperational;Error.captureStackTrace(this);}
}//继承 BaseError
class APIError extends BaseError {constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, description = 'internal server error') {super(name, httpCode, isOperational, description);}
}
为了简单起见,我只实现了一些 HTTP 状态码,你可以尝试添加更多状态码:
export enum HttpStatusCode {OK = 200,BAD_REQUEST = 400,NOT_FOUND = 404,INTERNAL_SERVER = 500,
}
同时,你可以根据你的需要和个人偏好对常见错误进行扩展:
class HTTP400Error extends BaseError {constructor(description = 'bad request') {super('NOT FOUND', HttpStatusCode.BAD_REQUEST, true, description);}
}
那么如何使用它呢? 很简单,就是抛出这种错误类型:
const user = await User.getUserById(1);
if (user === null)throw new APIError('NOT FOUND',HttpStatusCode.NOT_FOUND,true,'detailed explanation');
集中式 Node.js 错误处理组件
现在,我们准备构建 Node.js 错误处理系统的主要组件: 集中式错误处理组件。
构建集中式的错误处理组件通常是一个好主意,以便在处理错误时避免可能的代码重复。错误处理组件负责使捕获的错误变得可以理解,例如,通过向系统管理员发送通知、将事件传输到监视服务器中(如 Sentry)、打日志记录错误。
下图中我给出了处理错误的基本工作流程:
在代码的某些部分,错误会被捕获并传递给错误处理中间件:
try {userService.addNewUser(req.body).then((newUser: User) => {res.status(200).json(newUser);}).catch((error: Error) => {next(error)});
} catch (error) {next(error);
}
错误处理中间件是区分错误类型并将它们发送到集中式错误处理组件的好地方:
app.use(async (err: Error, req: Request, res: Response, next: NextFunction) => {if (!errorHandler.isTrustedError(err)) {next(err);}await errorHandler.handleError(err);
});
到目前为止,你应该可以想象到集中式组件应该是什么样子。不过请记住,这完全取决于你如何实现它。例如,它可能看起来像以下这样:
class ErrorHandler {public async handleError(err: Error): Promise<void> {await logger.error('Error message from the centralized error-handling component',err,);await sendMailToAdminIfCritical();await sendEventsToSentry();}public isTrustedError(error: Error) {if (error instanceof BaseError) {return error.isOperational;}return false;}
}
export const errorHandler = new ErrorHandler();
不过,有时候你会发现默认的 “console.error”
输出错误信息不是很好阅读。相反,以格式化的方式输出错误可能会更好,这样开发者可以更快速理解问题并确保它们得到修复。
这里,我向你推荐 winston
或 morgan
这样的可定制记录器。
例如,下面是一个定制的 winston
记录器:
const customLevels = {levels: {trace: 5,debug: 4,info: 3,warn: 2,error: 1,fatal: 0,},colors: {trace: 'white',debug: 'green',info: 'green',warn: 'yellow',error: 'red',fatal: 'red',},
};const formatter = winston.format.combine(winston.format.colorize(),winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),winston.format.splat(),winston.format.printf((info) => {const { timestamp, level, message, ...meta } = info;return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;}),
);class Logger {private logger: winston.Logger;constructor() {const prodTransport = new winston.transports.File({filename: 'logs/error.log',level: 'error',});const transport = new winston.transports.Console({format: formatter,});this.logger = winston.createLogger({level: isDevEnvironment() ? 'trace' : 'error',levels: customLevels.levels,transports: [isDevEnvironment() ? transport : prodTransport],});winston.addColors(customLevels.colors);}trace(msg: any, meta?: any) {this.logger.log('trace', msg, meta);}debug(msg: any, meta?: any) {this.logger.debug(msg, meta);}info(msg: any, meta?: any) {this.logger.info(msg, meta);}warn(msg: any, meta?: any) {this.logger.warn(msg, meta);}error(msg: any, meta?: any) {this.logger.error(msg, meta);}fatal(msg: any, meta?: any) {this.logger.log('fatal', msg, meta);}
}export const logger = new Logger();
它主要提供的是以格式化的方式在多个不同级别进行日志记录,颜色清晰,并根据运行时环境记录到错误日志文件中。这样做的好处是,你可以使用 winston
的内置 api
来监视和查询日志。此外,你可以使用日志分析工具来分析格式化的日志文件,以获得有关应用程序的更多有用信息。
到目前为止,我们主要讨论了如何处理操作错误,那开发者的代码逻辑造成的错误呢?
由于开发者的错误是意料之外的,它们是实际的 bug,可能导致应用程序最终处于错误的状态,并以意想不到的方式运行。那么,处理这些错误的最佳方法是“立即崩溃”,然后使用像 PM2
这样的自动重启器优雅地重新启动:
process.on('uncaughtException', (error: Error) => {errorHandler.handleError(error);if (!errorHandler.isTrustedError(error)) {process.exit(1);}
});
最后我想要提到的是处理未处理的 promise.reject
和 异常。
在开发 Node.js/Express
应用程序时,你可能会发现自己花了很多时间处理承诺。当你忘记处理 reject
时,会看到有关未处理 promise.reject
的警告信息。
除了日志记录之外,警告消息不会做太多事情,但是使用适当的回退和订阅 process.on('unhandledRejection',callback)
是一个不错的做法。你可以将其视为Node.js 的一种全局的错误处理程序。
典型的错误处理流程如下所示:
User.getUserById(1).then((firstUser) => {if (firstUser.isSleeping === false) throw new Error('He is not sleeping!');
});
...// 获取未处理的 reject 并将其扔给我们已有的另一个回退处理程序
process.on('unhandledRejection', (reason: Error, promise: Promise<any>) => {throw reason;
});process.on('uncaughtException', (error: Error) => {errorHandler.handleError(error);if (!errorHandler.isTrustedError(error)) {process.exit(1);}
});
结尾
现在,你是否意识到无论是在开发阶段还是在生产阶段错误处理可不是一个可选的功能,而是应用程序的一个必要部分。
在 Node.js 中的单个组件中处理错误的策略将确保开发人员节省宝贵的时间,并通过避免代码重复和丢失错误上下文来编写干净且可维护的代码。不得不说,它已经成为 Node.js 应用程序的必备保健品。
相关文章:

Node.js 应用的御用品: Node.js 错误处理系统
开发中,有些开发者会积极寻求处理错误,力求减少开发时间,但也有些人完全忽略了错误的存在。正确处理错误不仅意味着能够轻松发现和纠正错误,而且还意味着能够为大型应用程序开发出稳健的代码库。 特别是对于 Node.js 开发人员&am…...

K210-CanMV IDE开发软件
K210-CanMV IDE开发软件 界面功能简介连接设备临时运行开机运行程序 界面功能简介 区域①菜单栏:操作文件,使用工具等。 区域②快捷按钮:区域①中的文件和编辑中部分功能的快捷方式。 区域③连接设备:连接设备和程序控制按钮。 …...

0301yarnmapredude入门-hadoop-大数据学习
文章目录 1 MapReduce概述2 YARN2.1 yarn概述2.2 yarn与MapReduce关系2.3 yarn架构2.4 辅助角色 3 MapReduce & YARN部署3.1 集群规划3.2 配置文件3.3 分发配置文件 4 体验4.1 集群启动命令介绍4.2 提交MapReduce任务到YARN执行 结语 1 MapReduce概述 分布式计算是一种计算…...

大数据课程K15——Spark的TF-IDF计算Term权重
文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 了解Spark的TF-IDF算法概念; ⚪ 了解Spark的TF-IDF算法定义; ⚪ 了解Spark的TF-IDF算法案例; 一、TF-IDF算法概述 TF-IDF(term frequency–inverse document frequency)是一种用于信…...

【C语言】字符函数,字符串函数,内存函数
大家好!今天我们来学习C语言中的字符函数,字符串函数和内存函数。 目录 1. 字符函数 1.1 字符分类函数 1.2 字符转换函数 1.2.1 tolower(将大写字母转化为小写字母) 1.2.2 toupper(将小写字母转化为大写字母&…...

Spring MVC:域对象共享数据
Spring MVC 前言域对象共享数据使用 ModelAndView 向 request 域对象中共享数据使用 Map 、Model 或 ModelMap 向 request 域对象中共享数据使用 SesionAttributes 注解向 session 域对象中共享数据使用 Servlet API 向 application 域对象中共享数据 附 前言 在上一章中&…...

Vue框架--Vue中的计算属性
下面,我们来实现一个这样的需求。 实现输入框1和输入框2中文字内容的拼接。...

面试题-React(八):React如何实现插槽?
一、React插槽的概念 插槽是一种让组件变得更加灵活和可复用的技术。它允许我们在组件内部预留一些位置,然后在组件使用时填充这些位置,实现外部内容的嵌套。 二、实现React插槽的方法 在React中,实现插槽可以通过两种方式:pro…...

【前端demo】动态赋值CSS
文章目录 效果过程html实现oninput与onchange事件统一配置CSS 代码HTMLCSSJS 其他demo 效果 动态显示CSS样式,由:root统一配置。 效果预览:https://codepen.io/karshey/pen/BavLrwy 参考: Dynamic CSS Variables(codepen.io) 漫谈document…...

BlockUI专栏目录
文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C-CSDN博客 简介: BlockUI是一个设计NX对话框的工具,是官方推荐使用的对话框制作方法,能够与NX自身风格相统一,并且在实际…...

K8S获取连接token
1、创建一个具有管理员权限的账户 下载或拷贝文件到主机上,vi k8s-admin.yml --- apiVersion: v1 kind: ServiceAccount metadata:name: dashboard-adminnamespace: kube-system --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 met…...

CountDownLatch原理-(主线程等待子线程结束再执行)
CountDownLatch是共享锁的一种实现,它默认构造 AQS 的 state 值为 count。当线程使用countDown方法时,其实使用了tryReleaseShared方法以CAS的操作来减少state,直至state为0就代表所有的线程都调用了countDown方法。当调用await方法的时候,如果state不为0࿰…...

mybatis源码学习-3-解析器模块
写在前面,这里会有很多借鉴的内容,有以下三个原因 本博客只是作为本人学习记录并用以分享,并不是专业的技术型博客笔者是位刚刚开始尝试阅读源码的人,对源码的阅读流程乃至整体架构并不熟悉,观看他人博客可以帮助我快速入门如果只是笔者自己观看,难免会有很多弄不懂乃至理解错误…...

解决微信小程序recycle-view使用百分比单位控制宽高时出现的内容溢出问题
recycle-view是微信小程序官方推出的一个经过优化的长列表组件,但是在使用百分比单位控制高宽时有个内容溢出问题,虽然它提供了height和width的参数可以设置宽高,但每次写列表都需要去js里获取宽高并设置是较为麻烦的,所以现在来着…...

如何使用蚂蚁集团自动化混沌工程 ChaosMeta 做 OceanBase 攻防演练?
当前,业界主流的混沌工程项目基本只关注如何制造故障的问题,而经常做演练相关工作的工程师应该明白,每次演练时还会遇到以下痛点: 检测当前环境是否符合演练预设条件(演练准入); 业务流量是否满…...

在 Node.js 中使用 MongoDB 事务
MongoDB事务 事务介绍 在 MongoDB 中,对单个文档的操作是原子的。由于您可以使用嵌入的文档和数组来捕获单个文档结构中的数据之间的关系,而不是跨多个文档和集合进行规范化,因此这种单一文档的原子性消除了对多文档的需求许多实际用例的事务…...

IntelliJ IDEA的远程开发(Remote Development)
DEA的远程开发功能,可以将本地的编译、构建、调试、运行等工作都放在远程服务器上执行,而本地仅运行客户端软件进行常规的开发操作即可,官方给出的逻辑图如下,可见通过本地的IDE和服务器上的IDE backend将本地电脑和服务器打通&am…...

网络安全-信息收集简介
本文为作者学习文章,按作者习惯写成,如有错误或需要追加内容请留言(不喜勿喷) 本文为追加文章,后期慢慢追加 什么是信息收集 信息收集是指通过各种方式获取所需要的信息,以便我们在后续的渗透过程更好的…...

页面页脚部分CSS分享
先看效果: CSS部分:(查看更多) <style>body {display: grid;grid-template-rows: 1fr 10rem auto;grid-template-areas: "main" "." "footer";overflow-x: hidden;background: #F5F7FA;min…...

微信小程序slot插槽的介绍,以及如何通过uniapp使用动态插槽
微信小程序文档 - slots介绍 由上述文档看俩来,微信小程序官方并没有提及动态插槽内容。 uniapp文档 - slots介绍 uni官方也未提及关于动态插槽的内容 在实际使用中,直接通过 <<slot :name"item.xxx" /> 这种形式会报错ÿ…...

l8-d6 socket套接字及TCP的实现框架
一、socket套接字 /*创建套接字*/ int socket(int domain, int type, int protocol); /*绑定通信结构体*/ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); /*监听套接字*/ int listen(int sockfd, int backlog); /*处理客户端发起的连接࿰…...

ChatGPT AIGC 完成动态堆积面积图实例
先使用ChatGPT AIGC描述一下堆积面积图的功能与作用。 接下来一起看一下ChatGPT做出的动态可视化效果图: 这样的动态图案例代码使用ChatGPT AIGC完成。 将完整代码复制如下: <!DOCTYPE html> <html> <head><meta charset="utf-8"><tit…...

虹科产线实时数采检测方案——高速采集助力智能化升级
01 产线数采检测相关技术背景 1.1 典型场景 对于产线数采检测,让我们从典型的工厂场景开始介绍。 每个工位都有上位机监控下方的PLC控制器。指令、执行单元和作用对象的状态通过内置传感器进行采集和测量,反馈给PLC实现闭环控制。 工业4.0和智能制…...

用迅为RK3568开发板使用OpenCV处理图像颜色通道提取ROI
本小节代码在配套资料“iTOP-3568 开发板\03_【iTOP-RK3568 开发板】指南教程 \04_OpenCV 开发配套资料\07”目录下,如下图所示: 在计算机的色彩图像中存有三个通道,即 BGR 通道,根据三个颜色通道的亮度值来显示出不同的颜色&…...

低压配电室电力安全解决方案
低压电气安全监控运维系统是力安科技基于物联网核心技术自主开发的高可靠性安全监测系统。其工作原理是利用物联网、云计算、大数据、数字传感技术及RFID无线射频识别技术来获取低压配电回路电压、电流、温度、有功、无功、功率因数等全电量的采集及配电线路的漏电、温度的实时…...

【Windows 常用工具系列 11 -- 笔记本F5亮度调节关闭】
文章目录 笔记本 F 按键功能恢复 笔记本 F 按键功能恢复 使用笔记本在进行网页浏览时,本想使用F5刷新下网页,结果出现了亮度调节,如下图所示: 所以就在网上查询是否有解决这个问题的帖子,结果还真找到了:…...

Python小知识 - 【Python】如何使用Pytorch构建机器学习模型
【Python】如何使用Pytorch构建机器学习模型 机器学习是人工智能的一个分支,它的任务是在已有的数据集上学习,最终得到一个能够解决新问题的模型。Pytorch是一个开源的机器学习框架,它可以让我们用更少的代码构建模型,并且可以让模…...

使用Akka的Actor模拟Spark的Master和Worker工作机制
使用Akka的Actor模拟Spark的Master和Worker工作机制 Spark的Master和Worker协调工作原理 在 Apache Spark 中,Master 和 Worker 之间通过心跳机制进行通信和保持活动状态。下面是 Master 和 Worker 之间心跳机制的工作流程: Worker 启动后,…...

文心一言api接入如何在你的项目里使用文心一言
文心一言api接入在项目里接入文心一言 一、百度文心一言API二、使用步骤1、接口2、请求参数3、请求参数示例4、接口 返回示例 三、 如何获取appKey和uid1、申请appKey:2、获取appKey和uid 四、重要说明 一、百度文心一言API 基于百度文心一言语言大模型的智能文本对话AI机器人…...

Python匿名函数lambda(R与Python第五篇)
目录 一、为什么要引入“lambda函数”? 二、匿名函数的两种用法 参考: 本文来源:《Python全案例学习与实践》(2019年9月出版,电子工业出版社) Python允许使用一种无名的函数,称其为匿名函数…...