详细谈谈NodeJS进程是如何退出的

 更新时间:2021年7月14日 15:00  点击:1451

前言

有几种因素可以导致 NodeJS 进程退出。在这些因素中,有些是可预防的,比如代码抛出了一个异常;有些是不可预防的,比如内存耗尽。process 这个全局变量是一个 Event Emitter 实例,如果进程优雅退出,process 会派发一个 exit 事件。应用代码可以监听这个事件,来做最后的清理工作。

下面的表格列举了可以导致进程退出的因素。

操作 举例
手动退出 process.exit(1)
未捕获的异常 throw new Error()
未处理的 promise rejection Promise.reject()
未处理的 error 事件 EventEmitter#emit('error')
未处理的信号 kill <PROCESS_ID>

主动退出

process.exit(code) 是最直接的结束进程的方法。code 参数是可选的,可以为 0 ~ 255 之间任何数字,默认为 0。0 表示进程执行成功,非 0 数字表示进程执行失败。

当 process.exit() 被使用时,控制台不会有任何输出,如果我们想在进程推出的时候像控制台输出一些错误说明信息,则需要在调用之前显示的输出错误信息。

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

上面的代码直接退出了 NodeJS 进程,命令行没有任何输出信息。用户在遭遇进程退出的时候,无法获取有效的错误信息。

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

在上面的代码中,我们在进程退出之前输出的明确的错误信息。

process.exit() 的功能非常强大,但是我们不应该在工具库中使用。如果在工具库中遇到的错误,我们应该以异常的形式抛出,从而让应用代码决定如何处理这个错误。

Exceptions, Rejections 和 Emitted Errors

process.exit() 在应用启动配置检查等场景中非常有用,但是在处理运行时异常时,它并不适用,我们需要其他的工具。

比如当应用在处理一个 HTTP 请求时,一个错误不应该导致进程终止,相反,我们应该返回一个含有错误信息的响应。

Error 类可以包含描述错误发生的详细信息的数据,比如调用堆栈和错误文本。通常我们会定义特定场景的 XXXError,这些 XXXError 都继承制 Error 类。

当我们使用 throw 关键字,或者代码逻辑出错时,一个错误就会被抛出。此时,系统调用栈会释放,每个函数会退出,直到遇到一个 包裹了当前调用的 try/catch 语句。如果没有 try/catch 语句,则这个错误会被认为是未捕获的异常。

通常,在 NodeJS 应用中,我们会给 Error 类定义一个 code 属性,作为用来描述具体错误的错误码,这么做的优点是可以使错误码保持唯一,同时还能使得错误码是可读的。同时,我们也可以配合 message 属性来描述具体的错误信息。

当一个未捕获的异常抛出时,控制台会打印调用堆栈,同时进程退出,退出状态码为 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 是个 Event Emitter 实例,可以通过监听 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 Rejection 与抛出异常类似。我们可以通过调用 reject() 函数或者在 async 函数中抛出异常来是的 promise 到达 rejected 状态。下面的两段代码功能是相似的。

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

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

目前,在 NodeJS 14 中,Promise Rejection 不会导致进程退出,在后续的版本中,Promise Rejection 可能会导致进程退出。

下面是一段未捕获的 Promise Rejection 的控制台输出样例。

(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().

我们可以通过监听 unhandledRejection 事件来处理未捕获的 Rejection. 样例代码如下:

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

Event Emitter 是 NodeJS 中的基础模块,应用广泛。当 Event Emitter 的 error 事件未被处理时,Event Emitter 就会抛出一个错误,同时会导致进程退出。下面是一个 Event Emitter 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
}

因此,我们在使用 Event Emitter 的时候,要确保监听了 error 事件,这样在发生错误的时候,可以使得应用能够处理这些错误,避免奔溃。

信号

信号是操作信息提供了进程间通信机制。信号通常是一个数字,同时也可以使用一个字符串来标识。比如 SIGKILL 标识数字 9。不同的操作系统对信号的定义不同。下面表格里罗列的是基本通用的信号定义。

名称 数字 是否可处理 NodeJS 默认行为 信号的含义
SIGHUP 1 Yes 退出 父命令行被关闭
SIGINT 2 Yes 退出 命令行尝试中断,即 Ctrl + C
SIGQUIT 3 Yes 退出 命令行尝试退出,即 Ctrl + Z
SIGKILL 9 No 退出 强制进程退出
SIGUSR1 10 Yes 启动调试器 用户自定义信号
SIGUSR2 12 Yes 退出 用户自定义信号
SIGTERM 15 Yes 退出 进程优雅的退出
SIGSTOP 19 No 退出 进程被强制停止

这表格里,是否可处理表示这个信号是否可被进程接收并被处理。NodeJS 默认行为表示进程在接收到这个信号以后默认执行的动作。

我们可以通过如下方式来监听这些信号。

#!/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 信号的日志信息。新起一个命令行窗口,执行如下命令,PROCESS_ID 为上面程序输出的进程 ID。

kill -s SIGHUP <PROCESS_ID>

通过新起的命令行,我们向原来的那个程序进程发送了一个 SIGHUP 信号,原来的命令行窗口中会打印一行接收到了 SIGHUP 信号的日志信息。

在 NodeJS 代码中,进程也可以给其他进程发送信号。比如:

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

这段代码同样会在第一个命令行窗口中输出一行接收到了 SIGHUP 信号的日志。

如果我们要让第一个命令行窗口的进程退出,则可以通过下面的命令来实现。

kill -9 <PROCESS_ID>

在 NodeJS 中,信号通常被用作控制进程优雅的退出。比如,在 Kubernetes 中,当一个 pod 要退出时,k8s 会像 pod 内的进程发送一个 SIGTERM 的信号,同时启动一个 30 秒的定时器。应用程序有 30 秒的时间来关闭连接、保存数据等。如果 30 秒之后进程依然存活,k8s 会再发送一个 SIGKILL 来强制关闭进程。

小结

本文讲述了可以导致进程退出的几种因素,分别是:

  • 主动退出
  • 未捕获的异常、未处理的 promise rejection、未处理的 Event Emitter error 事件
  • 系统信号

到此这篇关于NodeJS进程是如何退出的文章就介绍到这了,更多相关NodeJS进程退出内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • NodeJS实现阿里大鱼短信通知发送

    本文给大家介绍的是nodejs实现使用阿里大鱼短信API发送消息的方法和代码,有需要的小伙伴可以参考下。...2016-01-20
  • C#启动进程的几种常用方法

    这篇文章主要介绍了C#启动进程的几种常用方法,实例分析了C#对系统进行的相关操作技巧,需要的朋友可以参考下...2020-06-25
  • Nodejs回调加超时限制两种实现方法

    这篇文章主要介绍了Nodejs回调加超时限制两种实现方法的相关资料,需要的朋友可以参考下...2017-06-15
  • C#获取所有进程的方法

    在本篇文章里小编给大家分享了关于C#获取所有进程的方法和步骤,有需要的朋友们跟着学习参考下。...2020-06-25
  • Nodejs中使用captchapng模块生成图片验证码

    本篇文章主要介绍了Nodejs中使用captchapng模块实现图片验证码,非常具有实用价值,需要的朋友可以参考下 ...2017-05-22
  • Nodejs学习item【入门手上】

    这篇文章主要介绍了Nodejs学习item【入门手上】,需要的朋友可以参考下...2016-05-09
  • nodejs 终端打印进度条实例代码

    本篇文章主要介绍了nodejs 终端打印进度条实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...2017-04-27
  • Node.js检测端口(port)是否被占用的简单示例

    大家有没有遇到过在开启本地服务时,有这么一种情况:当前端口已经被另一个项目使用了,导致服务开启失败。那么接下来,我们通过简简单单的示例代码来检测端口是否已经被占用。有需要的朋友们可以参考借鉴。...2016-10-02
  • Nodejs express框架一个工程中同时使用ejs模版和jade模版

    这篇文章主要介绍了Nodejs express框架一个工程中同时使用ejs模版和jade模版 的相关资料,需要的朋友可以参考下...2015-12-29
  • C#使用SendMessage实现进程间通信的方法

    这篇文章主要介绍了C#使用SendMessage实现进程间通信的方法,涉及C#中SendMessage方法的使用技巧,非常具有实用价值,需要的朋友可以参考下...2020-06-25
  • Nodejs下DNS缓存问题浅析

    本文给大家一起探讨nodejs下dns的缓存问题,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧...2016-11-22
  • C#获取进程的主窗口句柄的实现方法

    C#获取进程的主窗口句柄的实现方法,需要的朋友可以参考一下...2020-06-25
  • 详解Windows下安装Nodejs步骤

    本篇文章主要介绍了详解Windows下安装Nodejs步骤,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...2017-05-22
  • Nodejs 数组的队列以及forEach的应用详解

    这篇文章主要介绍了Nodejs 数组的队列以及forEach的应用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-25
  • C#中进程的挂起与恢复

    这篇文章主要介绍了C#中进程的挂起与恢复操作方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • 简单掌握Windows中C#启动外部程序进程的方法

    这篇文章主要介绍了Windows中C#启动外部程序进程的方法,例子中同时包括了进程关闭的方法,需要的朋友可以参考下...2020-06-25
  • node.js如何根据URL返回指定的图片详解

    这篇文章主要介绍了NODE.JS如何根据URL返回指定的图片详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-21
  • 详解C语言进程同步机制

    这篇文章主要介绍了详解C语言进程同步机制的的相关资料,文中代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-06-18
  • Node.js中使用jQuery的做法

    在Node.js中使用jQuery的做法,需要先安装jquery,npm install jquery ,安装后的版本是 3.1.0,本文介绍的非常详细,具有参考借鉴价值,感兴趣的朋友一起看下吧...2016-08-24
  • 详解Nodejs基于mongoose模块的增删改查的操作

    本篇文章主要介绍了Nodejs基于mongoose模块的增删改查的操作,Mongoose是MongoDB的一个对象模型工具,封装了MongoDB对文档的的一些增删改查等常用方法,让NodeJS操作Mongodb数据库变得更加灵活简单。 ...2016-12-31