node的里html中写script报错,Node 中脚本遭遇异常时如何安全退出

本文收录于 GitHub 山月行博客: shfshanyue/blog,内含我在实际工作中碰到的问题、关于业务的思考及在全栈方向上的学习前端工程化系列

Node进阶系列

一个 Node 相关的项目中,总是少不了跑脚本。跑一个脚本拉取配置、处理一些数据以及定时任务更是家常便饭。

在一些重要流程中能够看到脚本的身影:CI,用以测试、质量保障及部署等

Docker,用以构建镜像

Cron,用以定时任务

如果在这些重要流程中脚本出错无法及时发现问题,将有可能引发更加隐蔽的问题。

最近观察项目镜像构建,会偶尔发现一两个镜像虽然构建成功,但容器却跑不起来的情况。究其原因,是因为 Exit Code 的问题。

Exit Code什么是 exit code?

exit code 代表一个进程的返回码,通过系统调用 exit_group 来触发。在 POSIX 中,0 代表正常的返回码,1-255 代表异常返回码,一般主动抛出的错误码都是 1。在 Node 应用中使用 process.exitCode = 1 来代表因不期望的异常而中断。

这里有一张关于异常码的附表 Appendix E. Exit Codes With Special Meanings。

异常码在操作系统中随处可见,以下是一个关于 cat 命令的异常以及它的 exit code,并使用 strace 追踪系统调用。$ cat a

cat: a: No such file or directory

# 使用 strace 查看 cat 的系统调用

# -e 只显示 write 与 exit_group 的系统调用

$ strace -e write,exit_group cat a

write(2, "cat: ", 5cat: ) = 5

write(2, "a", 1a) = 1

write(2, ": No such file or directory", 27: No such file or directory) = 27

write(2, "\n", 1

) = 1

exit_group(1) = ?

+++ exited with 1 +++

从系统调用的最后一行可以看出,该进行的 exit code 是 1,并把错误信息输出到 stderr (标准错误的 fd 为2) 中

如何查看 exit code

从 strace 中可以来判断进程的 exit code,但是不够方便过于冗余,特别身处 shell 编程环境中。

有一种简单的方法,通过 echo $? 来确认返回码$ cat a

cat: a: No such file or directory

$ echo $?

1

throw new Error 与 Promise.reject 区别

以下是两段代码,第一段抛出一个异常,第二段 Promise.reject,两段代码都会如下打印出一段异常信息,那么两者有什么区别?function error () {

throw new Error('hello, error')

}

error()

// Output:

// /Users/shanyue/Documents/note/demo.js:2

// throw new Error('hello, world')

// ^

//

// Error: hello, world

// at error (/Users/shanyue/Documents/note/demo.js:2:9)

// at Object. (/Users/shanyue/Documents/note/demo.js:5:1)

// at Module._compile (internal/modules/cjs/loader.js:701:30)async function error () {

return new Error('hello, error')

}

error()

// Output:

// (node:60356) UnhandledPromiseRejectionWarning: Error: hello, world

// at error (/Users/shanyue/Documents/note/demo.js:2:9)

// at Object. (/Users/shanyue/Documents/note/demo.js:5:1)

// at Module._compile (internal/modules/cjs/loader.js:701:30)

// at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)

// (node:2787) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)

// (node:2787) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

在对上述两个测试用例使用 echo $? 查看 exit code,我们会发现 throw new Error() 的 exit code 为 1,而 Promise.reject() 的为 0。

从操作系统的角度来讲,exit code 为 0 代表进程成功运行并退出,此时即使有 Promise.reject,操作系统也会视为它执行成功。

这在 Dockerfile 与 CI 中将留有安全隐患。

Dockerfile 在 node 中的注意点

当使用 Dockerfile 构建镜像时,如果 RUN 的进程返回非0的返回码,构建就会失败。

而在 Node 中的错误处理中,我们倾向于所有的异常都交由 async/await 来处理,而当发生异常时,由于此时 exit code 为 0 并不会导致镜像构建失败。

这是一个浅显易懂的含 Promise.reject() 问题的镜像。FROM node:12-alpine

RUN node -e "Promise.reject('hello, world')"

构建镜像过程如下:即使在构建过程打印出了 unhandledPromiseRejection 信息,但是镜像仍然构建成功。$ docker build -t demo .

Sending build context to Docker daemon 33.28kB

Step 1/2 : FROM node:12-alpine

---> 18f4bc975732

Step 2/2 : RUN node -e "Promise.reject('hello, world')"

---> Running in 79a6d53c5aa6

(node:1) UnhandledPromiseRejectionWarning: hello, world

(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)

(node:1) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Removing intermediate container 79a6d53c5aa6

---> 09f07eb993fe

Successfully built 09f07eb993fe

Successfully tagged demo:latest

Promise.reject 脚本解决方案

能在编译时能发现的问题,绝不要放在运行时。所以,构建镜像或 CI 中需要执行 node 脚本时,对异常处理需要手动指定 process.exitCode = 1 来提前暴露问题runScript().catch(() => {

process.exitCode = 1

})

在构建镜像时,也有关于异常解决方案的建议:(node:1) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.ht... (rejection id: 1)

根据提示,--unhandled-rejections=strict 将会把 Promise.reject 的退出码设置为 1,并在将来的 node 版本中修正 Promise 异常退出码。$ node --unhandled-rejections=strict error.js

--unhandled-rejections=strict 的配置对 node 有版本要求:Added in: v12.0.0, v10.17.0

By default all unhandled rejections trigger a warning plus a deprecation warning

for the very first unhandled rejection in case no unhandledRejection hook

is used.

总结当进程结束的 exit code 为非 0 时,系统会认为该进程执行失败

通过 echo $? 可查看终端上一进程的 exit code

Node 中 Promise.reject 时 exit code 为 0

Node 中可以通过 process.exitCode = 1 显式设置 exit code

在 Node12+ 中可以通过 node --unhandled-rejections=strict error.js 执行脚本,视 Promise.reject 的 exit code 为 1

关注我

扫码添加我的微信,备注进群,加入高级前端进阶群

加我微信拉你进入面试交流群

欢迎关注公众号【全栈成长之路】,定时推送 Node 原创及全栈成长文章

欢迎关注全栈成长之路

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[Node 中脚本遭遇异常时如何安全退出]http://www.zyiz.net/tech/detail-146324.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值