作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
不难看出,有些人正在努力处理错误, 有些人甚至完全忽略了它. 正确地处理错误不仅意味着通过容易地发现错误和错误来减少开发时间,而且还意味着为大规模应用程序开发健壮的代码库.
特别是, 节点.js开发人员 有时会发现自己在处理各种错误时使用的代码不太干净, 错误地在所有地方应用相同的逻辑来处理它们. 他们只是不停地问自己 ”节点.Js不擅长处理错误?” or 如果没有,如何处理它们?” 我的回答是 “不,节点.Js一点也不差. 这取决于我们开发者.”
这是我最喜欢的解决方案之一.
首先,有必要对节点中的错误有一个清晰的认识.js. 一般来说,节点.Js的错误分为两类: 操作错误 和 程序员的错误.
考虑到这一点, 区分这两类错误应该没有问题:操作错误是应用程序的自然组成部分, 程序员错误是由开发人员造成的错误. 接下来的逻辑问题是: “为什么把它们分为两类并加以处理是有用的?”
没有对错误的清晰认识, 每当出现错误时,您可能希望重新启动应用程序. 当成千上万的用户正在使用应用程序时,由于“文件未找到”错误而重新启动应用程序是否有意义? 绝对不是.
但是程序员的错误呢? 当一个未知的错误出现,可能导致应用程序出现意想不到的滚雪球效应时,保持应用程序运行是否有意义? 当然不是!
假设您有一些使用异步JavaScript和节点的经验.在Js中,您可能在使用回调处理错误时遇到了一些缺点. 它们迫使您检查错误,一直到嵌套错误, 导致臭名昭著的“回调地狱”问题,使代码流难以遵循.
使用promise或async/await可以很好地替代回调. async/await的典型代码流如下所示:
const doAsyncJobs = async () => {
尝试{
Const result1 = await job1();
Const result2 = await job2(result1);
Const result3 = await job3(result2);
Return await job4(result3);
} catch (error) {
控制台.错误(错误);
} finally {
等待anywayDoThisJob ();
}
}
使用节点.js内置的错误对象是一个很好的实践,因为它包含直观和清晰的错误信息,如StackTrace, 大多数开发人员依赖哪个来跟踪错误的根源. 另外一些有意义的属性,如HTTP状态码和通过扩展错误类的描述,将使其更具信息性.
类Base错误扩展错误 {
公共只读名称:string;
public readonly httpCode: HttpStatusCode
public readonly isOperational: boolean;
构造函数(名称:string, httpCode: HttpStatusCode,描述:string, isOperational: boolean) {
超级(描述);
Object.setPrototypeOf(新.目标.原型);
这.Name = Name;
这.httpCode = httpCode;
这.isOperational = isOperational;
错误.captureStackTrace(这个);
}
}
//可以扩展Base错误
类API错误扩展Base错误
构造函数(name, httpCode = HttpStatusCode).INTERNAL_SERVER, isOperational = 真的, description = '内部服务器错误'){
super(name, httpCode, isOperational, description);
}
}
为了简单起见,我只实现了一些HTTP状态码, 但您可以稍后自由添加更多内容.
导出enum HttpStatusCode {
OK = 200,
Bad_request = 400,
Not_found = 404,
Internal_server = 500,
}
不需要扩展Base错误或API错误, 但是,根据您的需要和个人偏好扩展它来处理常见错误也是可以的.
类HTTP400错误扩展Base错误 {
构造函数(description = '错误请求'){
超级('NOT FOUND', HttpStatusCode.BAD_REQUEST, 真的, description);
}
}
那么如何使用它呢? 就把这个扔进去:
...
const user =等待用户.get用户ById (1);
If (user === null)
抛出新的API错误(
“没有找到”,
HttpStatusCode.NOT_FOUND,
真的,
“详细说明”
);
现在,我们已经准备好构建节点的主要组件.Js的错误处理系统:集中的错误处理组件.
构建集中的错误处理组件通常是一个好主意,以便在处理错误时避免可能的代码重复. 错误处理组件负责使捕获的错误可以被理解, 例如, 向系统管理员发送通知(如果需要), 将事件传输到像Sentry这样的监视服务.Io,并记录它们.
下面是处理错误的基本工作流程:
在代码的某些部分,错误被捕获并传递给错误处理中间件.
...
尝试{
userService.addNew用户(要求.身体).then((new用户: 用户) => {
res.状态(200).json(列出);
}).catch((error: 错误) => {
下(错误)
});
} catch (error) {
下(错误);
}
...
错误处理中间件是区分错误类型并将它们发送到集中式错误处理组件的好地方. 知道 最基本的 关于快速错误处理的问题肯定会有所帮助. (深入了解Express错误处理最佳实践也是值得的 使用承诺.)
应用程序.use(async (err: 错误, req: Request, res: Response, next: NextFunction) => {
if (!errorH和ler.isTrusted错误 (err)) {
下一个(错);
}
等待errorH和ler.h和le错误(错);
});
到现在为止, 可以想象集中式组件应该是什么样子,因为我们已经使用了它的一些功能. 请记住,这完全取决于你如何实现它,但它可能看起来像以下:
类错误H和ler {
public async h和le错误(err: 错误): Promise {
等待记录器.错误(
"来自集中错误处理组件的错误消息",
呃,
);
等待sendMailToAdminIfCritical ();
等待sendEventsToSentry ();
}
public isTrusted错误(error: error) {
if (error instanceof Base错误) {
返回错误.isOperational;
}
返回错误;
}
}
export const errorH和ler = new errorH和ler ();
有时,默认“控制台”的输出.“日志”使得很难跟踪错误. 而, 以格式化的方式打印错误可能会好得多,这样开发人员就可以快速理解问题并确保它们得到修复.
整体, 这将节省开发人员的时间,使其更容易跟踪错误,并通过增加错误的可见性来处理错误. 使用一个可定制的日志记录器是一个不错的决定 温斯顿 or 摩根.
这是一个定制的温斯顿记录器:
const customLevels = {
级别:{
跟踪:5
调试:4
信息:3,
警告:2
错误:1、
致命:0,
},
颜色:{
道:“白”,
调试:“绿色”,
信息:“绿色”,
警告:“黄色”,
错误:“红”,
致命的:“红”,
},
};
Const 格式ter = Winston.格式.结合(
温斯顿.格式.彩色化(),
温斯顿.格式.时间戳({格式: 'YYYY-MM-DD HH:mm:ss'}),
温斯顿.格式.长条木板(),
温斯顿.格式.printf((info) => {
Const{时间戳,级别,消息, ...Meta} = info;
${timestamp} [${level}]: ${message} ${
Object.键(元).长度 ? JSON.Stringify (meta, null, 2): "
}`;
}),
);
类记录器{
私有记录器:Winston.记录器;
构造函数(){
const productransport =新温斯顿.传输.文件({
文件名:“日志/错误.日志”,
水平:“错误”,
});
Const运输=新的温斯顿.传输.控制台({
格式:格式化程序,
});
这.记录器=温斯顿.createLogger ({
水平:isDevEnvironment () ? 'trace': 'error';
级别:customLevels.的水平,
传输:[isDevEnvironment () ? transport: prodTransport],
});
温斯顿.addColors (customLevels.颜色);
}
Trace (msg: any, meta .?: any) {
这.日志记录器.日志('trace', msg, meta);
}
调试(msg: any, meta?: any) {
这.日志记录器.调试(味精、元);
}
Info (msg: any, meta .?: any) {
这.日志记录器.信息(味精、元);
}
警告(msg: any, meta。?: any) {
这.日志记录器.警告(味精、元);
}
错误(msg: any, meta?: any) {
这.日志记录器.错误(味精、元);
}
致命的(msg: any, meta .?: any) {
这.日志记录器.日志('fatal', msg, meta);
}
}
export const 日志记录器 = new 日志记录器 ();
它主要提供的是以格式化的方式在多个不同级别进行日志记录, 色彩清晰, 并根据运行环境登录到不同的输出媒体. 这样做的好处是,您可以使用温斯顿的内置api来监视和查询日志. 此外, 您可以使用日志分析工具来分析格式化的日志文件,以获得有关应用程序的更多有用信息. 太棒了,不是吗?
到目前为止,我们主要讨论了如何处理操作错误. 程序员错误呢?? 处理这些错误的最佳方法是立即崩溃,然后使用像pm2这样的自动重启器优雅地重新启动——原因是程序员的错误是意料之外的, 因为它们是可能导致应用程序最终处于错误状态并以意想不到的方式运行的实际错误.
过程.on('uncaughtException', (error: 错误) => {
errorH和ler.h和le错误(错误);
if (!errorH和ler.isTrusted错误(错误)){
过程.退出(1);
}
});
最后但并非最不重要的是,我要提到处理未处理的承诺拒绝和异常.
在使用节点时,您可能会发现自己花了很多时间处理承诺.js /表达应用程序. 当您忘记处理拒绝时,不难看到有关未处理承诺拒绝的警告信息.
除了记录日志之外,警告消息不会做太多事情, 但使用一个体面的退路并订阅它是一个很好的做法 过程.(“unh和ledRejection”,回调)
. 您可以将其视为一种节点.Js全局错误处理程序.
典型的错误处理流程可能如下所示:
//在代码的某处
...
用户.get用户ById (1).then((first用户) => {
如果(first用户.isSleeping === false)抛出新的错误('He is not sleeping .!');
});
...
//获取未处理的拒绝并将其扔给我们已有的另一个回退处理程序.
过程.on('unh和ledRejection', (reason: 错误, promise: Promise) => {
把原因;
});
过程.on('uncaughtException', (error: 错误) => {
errorH和ler.h和le错误(错误);
if (!errorH和ler.isTrusted错误(错误)){
过程.退出(1);
}
});
当一切尘埃落定, 您应该意识到,错误处理不是一个可选的附加功能,而是应用程序的一个基本部分, 无论是在开发阶段还是在生产阶段.
在节点中的单个组件中处理错误的策略.Js将确保开发人员节省宝贵的时间,并通过避免代码重复和丢失错误上下文来编写干净且可维护的代码.
我希望您喜欢阅读本文,并发现所讨论的错误处理工作流程和实现对构建一个 健壮的节点代码库.js.
适当的错误处理使应用程序健壮,从而产生卓越的用户体验和提高生产力.
使用promise或async/await, 处理集中式组件中的错误, 处理未捕获的异常.
错误处理是所有应用程序的必备部分. 它可以防止应用程序容易出错,节省宝贵的开发时间.
通过订阅过程.(“unh和ledRejection”)的过程.(“uncaughtException”)
不,它不是. 这取决于开发者和他们选择的处理方式.
世界级的文章,每周发一次.
世界级的文章,每周发一次.