js 弹出层的点击事件影响到底层的点击事件_聊一聊 Node.js 错误处理

个人博客:https://blog.skrskrskrskr.com

错误分类

软件程序中,我们可以将错误大致分为外部错误和内部错误两大类

外部错误是正确编写的程序在运行时产生的错误。它并不是程序本身的 bug,更多是一些外部原因导致的问题,比如请求超时、服务器返回500、内存不足等。

而内部错误是程序里的 bug。比如传参类型错误、读取 undefined 的一个属性等。这类问题跟你选择的开发语言、开发者的编程经验、系统复杂度等因素息息相关,虽然无法避免,但可以通过修改代码来修复它。

对应到 Node.js 程序上,一般遇到以下四类错误: 1. 标准的 JavaScript 错误。例如 SyntaxError、RangeError、ReferenceError、TypeError等。 2. 由底层操作系触发的系统错误,例如试图打开不存在的文件。 3. 用户自定义错误。 4. 断言错误。这类错误通常来自 assert 模块。

注:本文中不区分错误和异常,都将其统称为错误。

错误处理

当错误发生后,我们需要第一时间去处理它。针对不同类型的错误,有不同的措施。处理错误的总体原则:

  1. 及时止损,防止系统级崩溃。
  2. 详细记录现场,方便分析原因。

外部错误

程序运行过程中,可能会遇到各种外部因素导致的问题,这些问题需要具体问题具体分析。我们没办法保证外部服务提供方的稳定性,但是遇到此类问题时,可以做一些事情,来保证我们的程序不至于直接崩溃。

举个例子,秒杀场景的业务经常会承受非常大的 QPS,在一波瞬间大流量的冲击,后端服务扛不住的话会报 5XX 错误。在后端服务挂掉后,我们可能会去读 redis 等缓存中的数据,用旧数据来兜底。而当 Node.js 应用也挂掉了,还可以在 Nginx 层进行 CDN 降级,给用户输出一个兜底的静态页。

还有些来自后端服务的错误,只需要进行简单的重试就能解决。如果要重试的话,要确定重试的次数,以及重试的间隔。

有人建议在发生错误后直接崩溃掉,防止错误扩散。个人认为其实是不合理的,会降低服务的可用性。我们可以在出现一些严重的错误后,先记录下错误,然后重启进程。在 Node.js 中,未捕获的 JavaScript 异常一直冒泡回到事件循环时,会触发 process.uncaughtException 事件。我们可以在事件回调中做错误上报,然后重启 Node.js 进程。这时,还需要借助 Cluster 来启动多个 Node 进程,保证单进程崩溃重启不会影响整体服务的可用性。实际的生产环境中,使用 PM2 来管理 Node.js 进程是一个更好的选项。

我们永远也无法阻止外部错误,它跟你的业务场景、用户终端等各种不可控的因素相关。但是我们如果做好监控、告警、日志、缓存等工作,可以方便程序员迅速定位/解决问题,从而将损失降至最低。

内部错误

同步场景

对于 JavaScript 错误,我们可以使用 throw 抛出,并用 try catch 来捕获住。

try {throw new Error('some error')
} catch(e) {console.error(e)
}

而且对于 throw 抛出的异常必须要 try catch 包裹,否则 Node.js 进程会直接退出。这种写法可以获取到完整的错误调用堆栈。比如:

fs.js:115throw err;^Error: ENOENT: no such file or directory, scandir '/Users/frank/code/work/wxapp/src/componentsa'at Object.readdirSync (fs.js:783:3)at getDirFilePaths (/Users/frank/code/m/demo/readdir.js:8:22)at Object.<anonymous> (/Users/frank/code/m/demo/readdir.js:27:15)at Module._compile (internal/modules/cjs/loader.js:688:30)at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)at Module.load (internal/modules/cjs/loader.js:598:32)at tryModuleLoad (internal/modules/cjs/loader.js:537:12)at Function.Module._load (internal/modules/cjs/loader.js:529:3)at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)at startup (internal/bootstrap/node.js:285:19)

众所周知,JS 函数调用会形成一系列的栈帧,为了尽可能的恢复错误发生现场,最好在错误上报时带上堆栈信息。Node.js 中,Error.captureStackTrace() 方法是 v8 引擎暴露出来的,处理错误堆栈信息的 API。

Error.captureStackTrace(targetObject[, constructorOpt]) 在 targetObject 中添加一个 .stack 属性。对该属性进行访问时,将以字符串的形式返回 Error.captureStackTrace() 语句被调用时的代码位置信息(即:调用栈历史)。

值得注意的是,它的第二个参数可以用来控制栈帧的终点。在一些底层库中,这个参数可以用来向开发者隐藏内部实现细节。

实际的生产环境中,我们可以使用 nested-error-stacks 这类 npm 包来采集堆栈信息,原理其实也是基于 Error.captureStackTrace()。

这里有个问题是:try catch 代码块是同步的,对于异步 API 发生的错误,它不能捕获到

比如下列代码:

try {setTimeout(() => {throw new Error('some error')}, 1000)
} catch(e) {console.log('some error...')
}

错误并不能被捕获住。这个跟 Node.js 的事件循环机制有关,因为异步任务是通过事件队列来实现的,每次从事件队列中取出一个函数来执行时,实际上这个函数是在调用栈的最顶层执行的,如果它抛出了一个异常,也是无法沿着调用栈回溯到这个异步任务的创建者的。

下面介绍下在异步流程中,我们应该怎么处理错误。

异步场景

Node.js 中常见异步场景包括三类: - Node.js style callback - Promise - EventEmitter

大部分异步 API 都遵循错误回调优先的约定,将 Error 作为 callback 的第一个参数来传递,这种风格比较类似函数式编程中的 Continuation-passing style。

fs.readFile(path, 'r', (err, data) => {if (err) {throw err} else {try {// handle data} catch(e) {}}
})

这种写法很容易造成回调地狱。另一方面,对于回调函数中的同步逻辑,我们还需要用 try catch 去单独处理,这导致错误逻辑的处理被分散了两处。Promise 被正式 ES6 标准化后,我们可以用 Promise 的链式调用来处理错误。

new Promise((resolve, reject) => {reject(new Error('some error'));}).then(() => {...}).then(() => {...}).catch(err => {});

这样,Promise 链上的错误都会在 catch 方法上捕获住。对于没有 catch 的 Promise 异常,会一直冒泡到顶层,在 process.unhandledRejection 事件上被捕获住。

还有一类是 EventEmitter 对象上的错误。它们会被分发到 error 事件上进行处理,比如 Stream 等。我们需要去为每一个流去监听 error 事件,否则会冒泡到process.uncaughtException 事件上去。

异步场景中,还有个问题就是,会丢失异步回调前的错误堆栈。原因还是上文提到的 Node.js 事件循环机制。

const foo = function () {throw new Error('some error')
}
const bar = function () {setTimeout(foo)
}
bar()

输出结果:

Error: some errorat Timeout.foo [as _onTimeout] (/Users/frank/code/m/demo/readdir.js:47:9)at ontimeout (timers.js:436:11)at tryOnTimeout (timers.js:300:5)at listOnTimeout (timers.js:263:5)at Timer.processTimers (timers.js:223:10)

可以看到丢失了 bar 的调用栈。然而在 Node.js 中,异步调用场景还挺多的,有什么办法可以将多个异步调用给串起来,获取到完整的调用链信息呢?答案是有的。Node.js v8+ 上提供了 async_hooks 模块,用来完善异步场景的监控。

async_hooks

async_hooks 提供了一些 API 用于跟踪 Node.js 中的异步资源的生命周期。有几个概念: - 每个异步函数的作用域,我们称之为 async scope。 - 每一个 async scope 中都有一个 asyncId, 用来标记当前作用域。相同 async scope 的 asyncId 也相同。每个异步资源在创建时 asyncId 全量递增的。 - 每一个 async scope 中都有一个 triggerAsyncId 表示当前函数是由哪个 async scope 触发生成的。 - 通过 asyncId 和 triggerAsyncId,我们可以获取到异步资源的调用链。 - async_hooks.createHooks 函数可以用来给每个异步资源添加 init/before/after/destory 等生命周期钩子函数。

console.log('global.asyncId:', async_hooks.executionAsyncId());  // global.asyncId: 1
console.log('global.triggerAsyncId:', async_hooks.triggerAsyncId()); // global.triggerAsyncId: 0
fs.open('./app.js', 'r', (err, fd) => {console.log('fs.open.asyncId:', async_hooks.executionAsyncId()); // fs.open.asyncId: 7console.log('fs.open.triggerAsyncId:', async_hooks.triggerAsyncId()); // fs.open.triggerAsyncId: 1
});

回调函数中的 triggerAsyncId 为 1,它等于 global scope 上的 asyncId。这样就可以拿到多个异步调用的调用链。

国内的赵坤大神写过一个 koa 日志中间件 koa-await-breakpoint,用于实现在每个 await 执行的语句前后进行自动打点工作。

// On top of the main file
const koaAwaitBreakpoint = require('koa-await-breakpoint')({name: 'api',files: ['./routes/*.js']
})
const Koa = require('koa')
const app = new Koa()
// Generally, above other middlewares
app.use(koaAwaitBreakpoint)
...
app.listen(3000)

每个请求到来时,生成一个 requestId 挂载到 ctx 上,通过 requestId 将日志串起来。核心原理是 hack 了模块的 require 方法(重载 Module.prototype._compile),用 esprima 将模块代码转成 AST,找到其中的 awaitExpression 节点,对其用日志函数包裹后重新插入到 AST,最后用 escodegen 将 AST 生成代码。其中还用到了 async_hooks,在日志函数中,基于 async_hooks 的 init 钩子中将异步调用关系存储到一个 Map 中,最终实现函数调用链的自动日志打点。

不过,使用 async_hooks 在目前有较严重的性能损耗。建议生产环境慎用。

a4ac8d2c940d11eb3418a597477e8c34.png

总结

错误可分为外部错误和内部错误两类。对外部错误的处理主要考验系统架构的设计,只有系统设计的足够健壮,才能够抵御各种外部挑战,并损失降到最低。对于内部错误,本文分别讨论了同步和异步两种场景,介绍了 Error.captureStackTrace()async_hooks 等 API 在收集错误堆栈、异步调用链上的用途,并结合 koa-await-breakpoint 源码,解释了 Node.js 自动化打点的核心原理。


参考链接:

  1. 胡子大哈. 深入理解 JavaScript Errors 和 Stack Traces. https://zhuanlan.zhihu.com/p/25338849
  2. Chuan's blog. 关于 Error.captureStackTrace. http://blog.shaochuancs.com/about-error-capturestacktrace
  3. joyent. Error Handling in Node.js. https://www.joyent.com/node-js/production/design/errors
  4. 王子亭. Node.js 错误处理实践. https://jysperm.me/2016/10/nodejs-error-handling
  5. 张佃鹏. 学习使用 Node.js 中 async-hooks 模块. https://zhuanlan.zhihu.com/p/53036228
  6. bmeurer. https://github.com/bmeurer/async-hooks-performance-impact
  7. http://nodejs.cn/api/errors.html#errors_errors

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/468885.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

老王带你理解算法复杂度O(1),O(N),O(N^2)

上图对应的是算法复杂度的图片&#xff0c;X轴对应的是n(问题规模)&#xff0c;Y轴对应的是执行的运行时间。 我们先从简单的复杂度解读O(1) 从上面的图片我们可以看到O(1)的复杂度是恒定的&#xff0c;一点波澜都没有&#xff0c;什么是O(1)呢&#xff0c;就比如你是一个酒店…

【PyAutoGUI操作指南】05 屏幕截图与图像定位:截图+定位单个目标+定位全部目标+灰度匹配+像素匹配+获取屏幕截图中像素的RGB颜色

6 屏幕截图与图像定位 PyAutoGUI可以拍摄屏幕截图&#xff0c;将其保存到文件中&#xff0c;并在屏幕中定位图像。OSX使用操作系统附带的screencapture命令。Linux使用scrot命令&#xff0c;可以通过运行sudo-apt-get-install-scrot来安装该命令。 功能介绍&#xff1a;一个需…

C语言-数组a 和a 的区别

面试经典题目 #include "stdio.h"int main() {int a[5] { 1,2,3,4,5 };int *ptr (int *)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));/*getchar是用VS编写方便查看输出*/getchar();return 0; }请思考一下上面的输出结果&#xff0c;如果你非常自…

前端为什么有的接口明明是成功回调却执行了.catch失败回调_前端进阶高薪必看-手写源码篇(高频技术点)...

前言此系列作为笔者之前发过的前端高频面试整理的补充 会比较偏向中高前端面试问题 当然大家都是从新手一路走过来的 感兴趣的朋友们都可以看哈初衷我相信不少同学面试的时候最怕的一个环节就是手写代码 大家一定听过这句话talk is cheap, show me the code 没事 此文章不仅包含…

C/C++函数指针与指针函数

前面说的话 面试的时候&#xff0c;经常有面试官问这个问题&#xff0c;在Linux内核里面也是经常被使用的&#xff0c;在看很多大神的代码里面&#xff0c;我们也经常遇到函数指针与指针函数&#xff0c;一样&#xff0c;如果你自己没问题了&#xff0c;就不用往下看了。 定义…

jsp人事管理系统_Jsp+Ssm+Mysql实现的医院人事管理系统源码附带视频运行教程

项目地址&#xff1a;jspssmmysql实现的医院人事管理系统源码附带视频运行教程|猿来入此【beta】多用户版IT项目教程源码分享网站​www.yuanlrc.com今天给大家演示的是一款由jspssm&#xff08;springspringMVCmybatis&#xff09;实现的医院人事管理系统&#xff0c;系统比较简…

RK3288/RK3399 CPU定频方法

直接上方法 查看cpu能支持的频率 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies 写入userspace说明要用户设定频率 echo userspace > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor 写入上面列出的cpu频率 echo 1608000 > …

notepad++正则表达式去掉关键字所在行

如下图 1、选择正则表达式 2、选择匹配大小写 3、^(.*)KERNEL(.*)$\n 加上\n就是把去除的行删除&#xff0c;不加就算把删除的行替换为空格

深度学习·理论篇(2023版)·第003篇深度学习和计算机视觉中的基础数学知识02:特征向量和特征值+矩阵乘法的几何意义(2)+奇异值分解+线性可分性和维度+非线性变换

&#x1f495;恭喜本博客浏览量达到两百万&#xff0c;CSDN内容合伙人&#xff0c;CSDN人工智能领域实力新星~ &#x1f9e1;本文章为2021版本迭代更新版本&#xff0c;在结合有效知识的基础上对文章进行合理的增加&#xff0c;使得整个文章时刻顺应时代需要 &#x1f9e1;本…

Oracle 控制文件管理

控制文件是一个很小的二进制文件(10MB左右)&#xff0c;含有数据库结构信息&#xff0c;包括数据文件和日志文件信息。控制文件在数据库创建时被自动创建&#xff0c;并在数据库发生物理变数时更新。控制文件被不断更新&#xff0c;在任何时候都要保证控制文件可用&#xff0c;…

数组超过预设的最大数组大小_工作表数组大小的扩展及意义

朋友们好&#xff0c;今日给大家继续讲解VBA数组与字典解决方案的第17讲&#xff0c;数组大小的扩充问题。这一讲的内容相对比较简单&#xff0c;在之前的章节中讲了数组与数组的计算规律&#xff0c;也是利用了数组的扩展原理。其实&#xff0c;两个数组计算时&#xff0c;参与…

Android ANR 实例分析

什么是ANR&#xff1f; 以下四个条件都可以造成ANR发生&#xff1a; InputDispatching Timeout&#xff1a;5秒内无法响应屏幕触摸事件或键盘输入事件BroadcastQueue Timeout &#xff1a;在执行前台广播&#xff08;BroadcastReceiver&#xff09;的onReceive()函数时10秒没…

【GIT 基础篇六】分支管理(创建与合并)

上篇我们整理了如何创建远程仓库&#xff0c;以及如何将本地文件上传至远程仓库&#xff0c;仓库创建好了&#xff0c;我们接下来就要准备开发了&#xff0c;对于使用git而言&#xff0c;通常的习惯就是一人一个分支&#xff0c;等测试无误再合并&#xff1b;又或者根据需求创建…

git 命令汇总

瞎扯 最近有几个留言想让写下git的内容&#xff0c;git是一个工具&#xff0c;主要是用来管理码农的代码的&#xff0c;理由很简单&#xff0c;码农写的代码太多&#xff0c;自己都不知道可能哪里出现了Crash。Linux也是因为git的出现&#xff0c;可以让世界上越来越多的人维护…

我的最佳队友之K8无线蓝牙键盘深度使用测评( Keychron K8 )

K8 无线蓝牙键盘深度使用测评&#xff08; Keychron K8 &#xff09;——500 元左右最适配 Mac 电脑的机械键盘 0.键盘参数&#xff1a; 首先我们在实际测评之前&#xff0c;看下这个键盘的具体参数&#xff0c;心里有个大致的了解~ 87键 国产佳达隆G轴 可选茶轴/红轴/青轴 蓝…

你见过哪些操蛋的代码?

NO.1#define TRUE FALSE //Happy debugging suckers快乐的去调试你的代码吧&#xff0c;哈哈NO.2#define NULL (::rand() % 2) // would be quite nice aswell嗯&#xff0c;这个代码也很不错NO.3#define if( if(!卧槽&#xff0c;这个代码更加叼&#xff0c;哈哈&#xff…

后序线索树怎样画图_算法新解刘新宇(二)二叉搜索树:数据结构中的“hello world”...

二叉搜索树BST定义&#xff1a;基于广义二叉树&#xff0c;一颗二叉树定义&#xff1a;或者为空 或者包含三部分&#xff1a;一个值&#xff0c;一个左分支和一个右分支。这两个分支也都是二叉树分支。一颗二叉搜索树是满足下面条件的二叉树&#xff1a;所有左分支的值都小于本…

Android 亮屏速度分析总结

前面聊的 最近在调试项目的亮屏速度&#xff0c;我们希望在按下power键后到亮屏这个时间能达到500MS以内&#xff0c;在Rockchip 3399和3288上面的时间都不能达到要求&#xff0c;因此引发了一系列的调试之路。 计算按下power键到亮屏的时间 Android 唤醒时间统计 刚开始的时…

英语学习中总结的阅读、段落匹配、选词填空技巧

1 阅读题 一般五道题都是围绕主旨来问的&#xff0c;所以后四个问题也能帮助第一题的解答&#xff0c;找共有词~~~串起来 文章一般都是新旧观点的碰撞&#xff0c;所以有时候他问的是旧观点&#xff0c;要看清楚他问的是新观点还是旧观点&#xff0c;这是个陷阱~ 2 段落匹配 反…

sql 账号查询一个表查询权限_一个查询语句引发的问题以及巨型表相关操作探索与思考...

背景&#xff1a;关于这个标题想了试了好几个总觉得欠那么点意思。大致情况是&#xff0c;在某服务支持中&#xff0c;1张大表4.5T左右&#xff0c;该表也是分区表。其中一个执行频繁的SQL写法有很大问题&#xff0c;导致巨表全量扫描&#xff0c;造成IO负载很大&#xff0c;业…