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" /> 这种形式会报错ÿ…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
