当前位置: 首页 > 后端技术 > Node.js

Node 中脚本遭遇异常时如何安全退出

时间:2023-04-04 00:58:13 Node.js

Node工程系列Node进阶系列脚本遇到异常如何安全退出在Node相关的项目中,运行脚本总是少不了的。运行脚本来拉取配置、处理一些数据和安排任务更是司空见惯。在一些重要的流程中可以看到脚本:CI,用于测试、质量保证和部署等。Docker,用于构建镜像Cron,用于定时任务。如果在这些重要过程中不能及时发现脚本错误,就会出现问题。可能会出现更隐蔽的问题。最近观察项目镜像构建,偶尔发现一两个镜像虽然构建成功,但是容器无法运行。原因是因为ExitCode的问题。退出代码什么是退出代码?exitcode表示进程的返回码,由系统调用exit_group触发。在POSIX中,0代表正常返回码,1-255代表异常返回码,一般主动抛出的错误码都是1。在Node应用中使用process.exitCode=1表示意外异常中止。这是异常代码的列表附录E.具有特殊含义的退出代码。异常代码在操作系统中随处可见,下面是cat命令的异常及其退出代码,使用strace跟踪系统调用。$catacat:a:Nosuchfileordirectory#使用strace查看cat的系统调用#-e只显示write和exit_group的系统调用$strace-ewrite,exit_groupcatawrite(2,"cat:",5cat:)=5write(2,"a",1a)=1write(2,":没有那个文件或目录",27:没有那个文件或目录)=27write(2,"\n",1)=1exit_group(1)=?+++exitedwith1+++从系统调用的最后一行可以看出,要执行的退出码为1,错误信息输出到stderr(标准的fderror是2)如何查看退出码from可以在strace中判断进程的退出码,但是不够方便,过于冗余,尤其是在shell编程环境下。有一种简单的方法可以通过echo$?$catacat:a:没有那个文件或目录$echo$?1thrownewError和Promise.reject的区别是下面两段代码,第一段抛出异常,第二段Promise.reject,两段代码都会打印出异常信息如下,那么两者有什么区别二?functionerror(){thrownewError('hello,error')}error()//Output:///Users/shanyue/Documents/note/demo.js:2//thrownewError('hello,world')//^////Error:hello,world//aterror(/Users/shanyue/Documents/note/demo.js:2:9)//atObject.(/Users/shanyue/Documents/note/demo.js:5:1)//在Module._compile(internal/modules/cjs/loader.js:701:30)asyncfunctionerror(){returnnewError('hello,error')}error()//Output://(node:60356)UnhandledPromiseRejectionWarning:E??rror:hello,world//aterror(/Users/shanyue/Documents/note/demo.js:2:9)//atObject.<匿名>(/Users/shanyue/Documents/note/demo.js:5:1)//atModule._compile(internal/modules/cjs/loader.js:701:30)//atObject.Module._extensions..js(内部/模块/cjs/loader.js:712:10)//(node:2787)UnhandledPromiseRejectionWarning:未处理的承诺拒绝。此错误的起因是在没有catch块的情况下在异步函数内部抛出,或者是拒绝了未使用.promise处理的承诺。抓住()。要在未处理的promise拒绝时终止节点进程,请使用CLI标志`--unhandled-rejections=strict`(参见https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode)。(拒绝ID:1)//(节点:2787)[DEP0018]DeprecationWarning:未处理的承诺拒绝已弃用。将来,未处理的承诺拒绝将以非零退出代码终止Node.js进程。在上面两个测试用例中使用了echo$?查看退出码,我们会发现thrownewError()的退出码是1,Promise.reject()的退出码是0。从操作系统的角度来看,0的退出码意味着:进程运行成功并退出,即使有Promise.reject,操作系统也会认为是执行成功。这在Dockerfile和CI中留下了安全隐患。node中Dockerfile注意事项使用Dockerfile构建镜像时,如果RUN进程返回非零返回码,会导致构建失败。在Node中的错误处理中,我们倾向于将所有的异常都交给async/await处理,当出现异常时,此时exitcode为0,不会导致镜像构建失败。这是Promise.reject()问题的易于阅读的镜像。FROMnode:12-alpineRUNnode-e"Promise.reject('hello,world')"构建镜像的过程如下:即使在构建过程中打印了unhandledPromiseRejection消息,仍然构建成功。$dockerbuild-tdemo。将构建上下文发送到Docker守护进程33.28kBStep1/2:FROMnode:12-alpine--->18f4bc975732Step2/2:RUNnode-e"Promise.reject('hello,world')"--->运行在79a6d53c5aa6(node:1)UnhandledPromiseRejectionWarning:hello,world(node:1)UnhandledPromiseRejectionWarning:未处理的承诺拒绝。这个错误要么是在没有catch块的情况下在异步函数内部抛出,要么是因为拒绝了一个没有用.catch()处理的承诺。要在未处理的promise拒绝时终止节点进程,请使用CLI标志`--unhandled-rejections=strict`(参见https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode)。(rejectionid:1)(node:1)[DEP0018]DeprecationWarning:未处理的承诺拒绝已弃用。将来,未处理的承诺拒绝将以非零退出代码终止Node.js进程。删除中间容器79a6d53c5aa6--->09f07eb993feSuccessfullybuilt09f07eb993feSuccessfullytaggeddemo:latestPromise.reject脚本解决方案可以在编译时找到,永远不要放在运行时。所以在构建镜像或者CI需要执行node脚本时,需要手动指定process.exitCode=异常处理1提前暴露问题runScript().catch(()=>{process.exitCode=1})在构建镜像的时候,也有异常解决的建议:(node:1)UnhandledPromiseRejectionWarning:Unhandledpromiserejection。这个错误要么是在没有catch块的情况下在异步函数中抛出,要么是拒绝了一个没有用.catch()处理的承诺。要在未处理的承诺拒绝时终止节点进程,请使用CLI标志--unhandled-rejections=strict(请参阅https://nodejs.org/api/cli.ht...(rejectionid:1)根据提示,--unhandled-rejections=strict会将Promise.reject的exitcode设置为1,未来在node版本中固定Promiseexceptionexitcode。$node--unhandled-rejections=stricterror.js--unhandled-rejections=strict配置对node有版本要求:添加于:v12.0.0,v10.17.0默认情况下所有未处理的拒绝都会触发警告加上弃用警告如果没有使用unhandledRejection挂钩,则会出现第一个未处理的拒绝。综上所述,当进程结束的退出码不为零时,系统会认为进程执行失败。可以在终端上通过echo$?查看上一个进程的exitcodeNode中Promise.reject时的exitcode?在Node中,可以通过process.exitCode=1显式设置退出码。在Node12+中,可以通过node--unhandled-rejections=stricterror.js执行脚本,将Promise.reject的退出码视为1。关注我扫码加我微信,备注进群,加入进阶前端进阶群=430&f=jpeg&s=38173"alt="加我微信拉你进面试交流群">

加我微信拉你进面试交流群
欢迎关注公众号【全栈成长之路】,定期推送Node原创和全栈成长文章<图>
欢迎关注全栈成长之路