弄死 Node.js 进程有几种方法

有几个缘由会致使 Node.js 进程终止。其中一些是能够避免的,例如抛出错误时,而另外一些是没法防止的,例如内存不足。全局 process 是一个 Event Emitter 实例,当执行正常退出时,将发出一个 exit 事件。而后程序代码能够经过侦听这个事件来执行最后的同步清理工做。前端

下面是能够主动触发进程终止的一些方法:node

操做 例子
手动流程退出 process.exit(1)
未捕获的异常 throw new Error()
未兑现的 promise Promise.reject()
忽略的错误事件 EventEmitter#emit('error')
未处理的信号 $ kill <PROCESS_ID>

其中有许可能是属于偶然被触发的,例如未捕获的错误或未处理的 promise,可是其中也有为了直接使进程终止而建立的。程序员

进程退出

使用 process.exit(code) 来终止进程是最直接的方法。这在当你知道本身的过程已经到了生命周期的尽头时很是有用。 code 值是可选的,默认值为0,最大能够设为 255。0 表示进程运行成功,而任何非零的数字都表示发生了问题。这些值能够被许多不一样的外部工具使用。例如当测试套件运行时,非零值表示测试失败。面试

直接调用 process.exit() 时,不会向控制台写入任何隐式文本。若是你编写了以错误表示形式调用此方法的代码,则你的代码应该用户输出错误来帮助他们解决问题。例如运行如下代码:shell

$ node -e "process.exit(42)"
$ echo $?

在这种状况下,单行的 Node.js 程序不会输出任何信息,尽管 shell 程序确实会打印退出状态。遇到这样的进程退出,用户将没法理解究竟发生了什么事情。因此要参考下面这段程序配置错误时会执行的代码:segmentfault

function checkConfig(config) {
  if (!config.host) {
    console.error("Configuration is missing 'host' parameter!");
    process.exit(1);
  }
}

在这种状况下,用户没会很清楚发生了什么。他们运行这个程序,将错误输出到控制台上,而且他们可以纠正这个问题。promise

process.exit() 方法很是强大。尽管它在程序代码中有本身的用途,但实际上绝对不该该将其引入可重用的库中。若是在库中确实发生了错误,则应抛出这个错误,以便程序能够决定应该如何处理它。bash

exceprion、rejection 和发出的 Error

虽然 process.exit() 颇有用,但对于运行时错误,你须要使用其余工具。例如当程序正在处理 HTTP 请求时,通常来讲错误不该该终止进程,而是仅返回错误响应。发生错误的位置信息也颇有用,这正是应该抛出 Error 对象的地方。服务器

Error 类的实例包含对致使错误的缘由有用的元数据,例如栈跟踪信息和消息字符串。从 Error 扩展你本身的错误类是很常见的操做。单独实例化 Error 不会有太多反作用,若是发生错误则必须抛出。微信

在使用 throw 关键字或发生某些逻辑错误时,将引起 Error。发生这种状况时,当前栈将会“展开”,这意味着每一个函数都会退出,直到一个调用函数将调用包装在 try/catch 语句中为止。遇到此语句后,将调用 catch 分支。若是错误没有被包含在 try/catch 中,则该错误被视为未捕获。

虽然你应该使用带有 Errorthrow 关键字,例如 throw new Error('foo'),但从技术上讲,你能够抛出任何东西。一旦抛出了什么东西,它就被认为是一个例外。抛出 Error 实例很是重要,由于捕获这些错误的代码极可能会指望获得错误属性。

Node.js 内部库中经常使用的另外一种模式是提供一个 .code 属性,该属性是一个字符串值,在发行版之间应保持一致。好比错误的 .code 值是 ERR_INVALID_URI,即便是供人类可读的 .message 属性可能会更改,但这个 code 值也不该被更改。

可悲的是,一种更经常使用的区分错误的模式是检查 .message 属性,这个属性一般是动态的,由于可能回须要修改拼写错误。这种方法是很冒险的,也是容易出错的。 Node.js 生态中没有完美的解决方案来区分全部库中的错误。

当引起未捕获的错误时,控制台中将打印栈跟踪信息,而且进程将回以退出状态 1 终止。这是此类异常的例子:

/tmp/foo.js:1
throw new TypeError('invalid foo');
^
Error: invalid foo
    at Object.<anonymous> (/tmp/foo.js:2:11)
    ... TRUNCATED ...
    at internal/main/run_main_module.js:17:47

上面的栈跟踪片断代表错误发生在名为 foo.js 的文件的第 2 行第 11 列。

全局的 process 是一个事件发射器,能够经过侦听 uncaughtException 事件来拦截未捕获的错误。下面是一个使用它的例子,在退出前拦截错误以发送异步消息:

const logger = require('./lib/logger.js');
process.on('uncaughtException', (error) => {
  logger.send("An uncaught exception has occured", error, () => {
    console.error(error);
    process.exit(1);
  });
});

Promise 拒绝与抛出错误很是类似。若是 Promise 中的 reject() 方法被调用,或者在异步函数中引起了错误,则 Promise 能够拒绝。在这方面,如下两个例子大体相同:

Promise.reject(new Error('oh no'));

(async () => {
  throw new Error('oh no');
})();

这是输出到控制台的消息:

(node:52298) UnhandledPromiseRejectionWarning: Error: oh no
    at Object.<anonymous> (/tmp/reject.js:1:16)
    ... TRUNCATED ...
    at internal/main/run_main_module.js:17:47
(node:52298) 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().

与未捕获的异常不一样,从 Node.js v14 开始,这些 rejection 不会使进程崩溃。在将来的 Node.js 版本中,会使当前进程崩溃。当这些未处理的 rejection 发生时,你还能够拦截事件,侦听 process 对象上的另外一个事件:

process.on('unhandledRejection', (reason, promise) => {});

事件发射器是 Node.js 中的常见模式,许多对象实例都从这个基类扩展而来,并在库和程序中使用。它们很是欢迎,值得和 error 与 rejection 放在一块儿讨论。

当事件发射器发出没有侦听器的 error 事件时,将会抛出所发出的参数。而后将抛出出一个错误并致使进程退出:

events.js:306
    throw err; // Unhandled 'error' event
    ^
Error [ERR_UNHANDLED_ERROR]: Unhandled error. (undefined)
    at EventEmitter.emit (events.js:304:17)
    at Object.<anonymous> (/tmp/foo.js:1:40)
    ... TRUNCATED ...
    at internal/main/run_main_module.js:17:47 {
  code: 'ERR_UNHANDLED_ERROR',
  context: undefined
}

确保在你使用的事件发射器实例中侦听 error 事件,以便你的程序能够正常处理事件而不会崩溃。

信号

信号是操做系统提供的机制,用于把用数字表示的消息从一个程序发送到另外一个程序。这些数字一般用等价的常量字符串来表示。例如,信号 SIGKILL 表明数字信号 9。信号能够有不一样的用途,但一般用于终止程序。

不一样的操做系统能够定义不一样的信号,可是下面列表中的信号通常是通用的:

名称 编号 可处理 Node.js 默认 信号用途
SIGHUP 1 终止 父终端已关闭
SIGINT 2 终止 终端试图中断,按下 Ctrl + C
SIGQUIT 3 终止 终端试图退出,按下 Ctrl + D
SIGKILL 9 终止 进程被强行杀死
SIGUSR1 10 启动调试器 用户定义的信号1
SIGUSR2 12 终止 用户定义的信号2
SIGTERM 12 终止 表明优雅的终止
SIGSTOP 19 终止 进程被强行中止

若是程序能够选择实现信号处理程序,则 Handleable 一列则为。为的两个信号没法处理。 Node.js 默认 这一列告诉你在收到信号时,Node.js 程序的默认操做是什么。最后一个信号用途指出了信号对应的做用。

在 Node.js 程序中处理这些信号能够经过侦听 process 对象上的更多事件来完成:

#!/usr/bin/env node
console.log(`Process ID: ${process.pid}`);
process.on('SIGHUP', () => console.log('Received: SIGHUP'));
process.on('SIGINT', () => console.log('Received: SIGINT'));
setTimeout(() => {}, 5 * 60 * 1000); // keep process alive

在终端窗口中运行这个程序,而后按 Ctrl + C,这个进程不会被终止。它将会声明已接收到 SIGINT 信号。切换到另外一个终端窗口,并根据输出的进程 ID 值执行如下命令:

$ kill -s SIGHUP <PROCESS_ID>

这演示了一个程序怎样向另外一个程序发送信号,而且在第一个终端中运行的 Node.js 程序中输出它所接收到的 SIGHUP 信号。

你可能已经猜到了,Node.js 也能把命令发送到其余程序。能够用下面的命令以把信号从临时的 Node.js 进程发送到你现有的进程:

$ node -e "process.kill(<PROCESS_ID>, 'SIGHUP')"

这还会在你的第一个程序中显示 SIGHUP 消息。如今,若是你想终止第一个进程,要运行下面的命令向其发送不能处理的 SIGKILL 信号:

$ kill -9 <PROCESS_ID>

这时程序应该结束。

这些信号在 Node.js 程序中常常用于处理正常的关闭事件。例如,当 Kubernetes Pod 终止时,它将向程序发送 SIGTERM 信号,以后启动 30 秒计时器。而后程序能够在这 30 秒内正常关闭本身,关闭链接并保存数据。若是该进程在此计时器后仍保持活动状态,则 Kubernetes 将向其发送一个 SIGKILL

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章: