如何在退出时执行异步操作

Posted

技术标签:

【中文标题】如何在退出时执行异步操作【英文标题】:How to perform an async operation on exit 【发布时间】:2017-03-27 05:35:20 【问题描述】:

我一直在尝试在我的进程终止之前执行异步操作。

说“终止”是指终止的所有可能性:

ctrl+c 未捕获的异常 崩溃 代码结束 任何东西..

据我所知,exit 事件可以做到这一点,但用于同步操作。

阅读 Nodejs 文档后,我发现 beforeExit 事件用于异步操作,但是:

对于导致显式终止的条件,例如调用 process.exit() 或未捕获的异常,不会发出 'beforeExit' 事件。

'beforeExit' 不应用作 'exit' 事件的替代品,除非打算安排额外的工作。

有什么建议吗?

【问题讨论】:

如何将所有代码放在单独的文件中(例如 afterExit.js),当退出事件发生时运行require('child_process').exec('node afterExit.js');,这只是一个想法 @Molda 嘿,我又把这个问题留了一段时间,现在重新开始工作,想像你建议的那样使用和执行外部脚本将是一个简单的解决方案,但我遇到了一个误解如何将任何变量传递给该脚本,因为“afterExit”代码取决于父级的状态,任何帮助将不胜感激! 您可以将一些变量作为参数传递,例如exec('node afterExit.js some_param some_other_param'); 然后使用 process.args 访问,或者创建一个包含所需数据的 json 文件并将该文件的路径传递给您的脚本并从 afterExit.js 加载该文件 @Molda 经过几天处理child_process exec,spawn,我回来了。 fork 方法,如果你能看看我刚刚发布的这个问题,我会很高兴***.com/questions/41209901/… 【参考方案1】:

您可以在退出前捕获信号并执行异步任务。这样的东西会在退出之前调用 terminator() 函数(甚至代码中的 javascript 错误):

process.on('exit', function () 
    // Do some cleanup such as close db
    if (db) 
        db.close();
    
);

// catching signals and do something before exit
['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT',
    'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM'
].forEach(function (sig) 
    process.on(sig, function () 
        terminator(sig);
        console.log('signal: ' + sig);
    );
);

function terminator(sig) 
    if (typeof sig === "string") 
        // call your async task here and then call process.exit() after async task is done
        myAsyncTaskBeforeExit(function() 
            console.log('Received %s - terminating server app ...', sig);
            process.exit(1);
        );
    
    console.log('Node server stopped.');

在评论中添加要求的详细信息:

来自节点documentation 的信号解释,此链接参考标准POSIX signal names 信号应该是字符串。但是,我已经看到其他人已经完成了检查,因此可能还有其他一些我不知道的意外信号。只是想在调用 process.exit() 之前确定一下。我认为无论如何检查都不会花费太多时间。 对于 db.close(),我想这取决于您使用的驱动程序。无论是异步同步。即使它是异步的,并且您在 db 关闭后不需要做任何事情,那么它应该没问题,因为 async db.close() 只是发出关闭事件,并且无论您的服务器是否退出,事件循环都会继续处理它。

【讨论】:

嗨,本,感谢您的回答。您能否提供有关此代码的更多解释?我熟悉的信号是SIGINTexit,其他的是什么?另外,你为什么要检查sig 是否是string?并假设 db.close 是异步的,它不会在那里完成吗? 说实话,我不知道,因为我已经十多年没有使用 Windows,但我认为它应该可以工作,因为这是标准的 node.js 代码。 Windows 上的 Node.js 应该知道如何解释它。 @KesemDavid 实际上就在这里。在db.close() 是异步的(很可能)事件中,从'exit' 事件处理程序does not guarantee its execution 调用if。查看下面的一些答案以获得更严格的实现。 SIGILL 是否意味着SIGKILL 正如@NicolásFantone 提到的,事件处理程序是同步的。他链接的参考资料说“侦听器函数只能执行同步操作。”您的终结器 fn 被调用,但不保证执行异步工作完成。【参考方案2】:

使用 beforeExit 钩子

'beforeExit' 事件在 Node.js 清空其事件循环并且没有其他工作要安排时发出。通常情况下,Node.js 进程会在没有安排工作时退出,但是注册在 'beforeExit' 事件上的监听器可以进行异步调用,从而导致 Node.js 进程继续运行。

process.on('beforeExit', async () => 
    await something()
    process.exit(0) // if you don't close yourself this will run forever
);

【讨论】:

他们在文档中说,当 process.exit() 在代码中的某处显式调用时,或者发生未发生的异常时,不会发出 'beforeExit',这使得它的使用非常有限。来源:nodejs.org/api/process.html#process_event_beforeexit【参考方案3】:

这是我对此的看法。在这里作为代码 sn-p 发布有点长,所以分享一个 Github 要点。

https://gist.github.com/nfantone/1eaa803772025df69d07f4dbf5df7e58

这很简单。你可以这样使用它:

'use strict';
const beforeShutdown = require('./before-shutdown');

// Register shutdown callbacks: they will be executed in the order they were provided
beforeShutdown(() => db.close());
beforeShutdown(() => server.close());
beforeShutdown(/* Do any async cleanup */);

上面将监听一组特定的系统信号(SIGINT,又名 Ctrl + C,默认情况下是 SIGTERM)并调用每个处理程序在关闭整个过程之前订购。

它也是,

支持async 回调(或返回Promise)。 警告关闭处理程序失败,但防止错误/拒绝冒泡。 如果处理程序在一段时间后未返回(默认为 15 秒),则强制关闭。 可以从代码库中的任何模块注册回调。

【讨论】:

你能解释一下它是如何支持异步回调的吗?您的要点依赖于 process.once()process.once('SIGTERM', handler) 的处理程序是一个同步函数。我确信调用了异步回调,但在其返回的承诺完成之前,它不能保证运行。你有测试表明它会吗?【参考方案4】:

结合答案+处理未捕获的异常和承诺拒绝

async function exitHandler(evtOrExitCodeOrError: number | string | Error) 
  try 
    // await async code here
    // Optionally: Handle evtOrExitCodeOrError here
   catch (e) 
    console.error('EXIT HANDLER ERROR', e);
  

  process.exit(isNaN(+evtOrExitCodeOrError) ? 1 : +evtOrExitCodeOrError);


[
  'beforeExit', 'uncaughtException', 'unhandledRejection', 
  'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 
  'SIGABRT','SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 
  'SIGUSR2', 'SIGTERM', 
].forEach(evt => process.on(evt, exitHandler));

【讨论】:

你没有忘记退出事件吗? @LeoPucciBr 我相信beforeExit 已经覆盖了这条路

以上是关于如何在退出时执行异步操作的主要内容,如果未能解决你的问题,请参考以下文章

php 异步执行脚本

如何在 MVC4 中执行异步调用操作方法

Promise如何使用?全方位解析,5分钟搞懂异步#yyds干货盘点#

如何在 Redux 中显示一个执行异步操作的模态对话框?

如何在angularjs执行多个异步操作后再执行指定操作

如果结果已经可用,则仅在单击按钮时启动异步任务并执行操作,否则等待结果然后执行操作