NodeJS捕获SIGINT(CTRL+C)时如何在退出前保存应用状态?
Node.js 捕获 SIGINT 保存计算数据的解决方案
先修正代码里的基础错误
你的现有代码存在几处语法问题,先做调整:
const filesystem = require('fs'); process.on('SIGINT', function onSigInt() { console.log('SIGINT ', new Date().toISOString()); shutdown(); }) function shutdown() { process.exit(); } async function run() { let allData = []; try { // 修正:使用读取到的fileData变量而非未定义的data const fileData = filesystem.readFileSync('output.json', {encoding:'utf-8', flag:'r'}); allData = JSON.parse(fileData.toString()); // 修正:模板字符串变量插入格式 console.log(`Loaded ${allData.length} records from file`); } catch (err) { console.log('No file to load data from, continuing with empty dataset'); } while(true) { doCalculation(allData); } filesystem.writeFileSync('output.json', JSON.stringify(allData, null, 4)); }
核心问题分析
- 同步无限循环阻塞事件循环:
while(true)是同步代码,会彻底卡住Node.js的事件循环,导致SIGINT信号的回调根本无法执行——Node需要等同步代码执行完毕,才会处理事件队列中的信号。 - allData的访问权限:当前
allData定义在run函数内部,shutdown函数无法直接访问它,但完全不需要使用全局变量解决这个问题。
不用全局变量的实现方式
方式1:模块级作用域变量(推荐,简单直接)
把allData放在模块顶层(属于当前模块的作用域,而非全局),让run和shutdown都能访问,同时不会污染全局环境:
const filesystem = require('fs'); // 模块级变量,仅当前模块可访问,不属于全局 let allData = []; process.on('SIGINT', function onSigInt() { console.log('SIGINT ', new Date().toISOString()); shutdown(); }) function shutdown() { // 先保存数据再退出 filesystem.writeFileSync('output.json', JSON.stringify(allData, null, 4)); console.log('数据已保存,程序退出'); process.exit(); } async function run() { // 加载数据到模块级的allData try { const fileData = filesystem.readFileSync('output.json', {encoding:'utf-8', flag:'r'}); allData = JSON.parse(fileData.toString()); console.log(`Loaded ${allData.length} records from file`); } catch (err) { console.log('No file to load data from, continuing with empty dataset'); } // 将同步循环改为非阻塞形式,避免卡住事件循环 async function loop() { while(true) { doCalculation(allData); // 让出事件循环,给信号处理留执行时间 await new Promise(resolve => setTimeout(resolve, 0)); } } await loop(); // 正常结束时保存数据 filesystem.writeFileSync('output.json', JSON.stringify(allData, null, 4)); } // 启动程序 run();
方式2:闭包(将shutdown定义在run内部)
利用JavaScript闭包特性,让shutdown直接捕获run函数内的allData变量,无需额外作用域声明:
const filesystem = require('fs'); async function run() { let allData = []; try { const fileData = filesystem.readFileSync('output.json', {encoding:'utf-8', flag:'r'}); allData = JSON.parse(fileData.toString()); console.log(`Loaded ${allData.length} records from file`); } catch (err) { console.log('No file to load data from, continuing with empty dataset'); } // 在run内部定义shutdown,通过闭包访问allData function shutdown() { filesystem.writeFileSync('output.json', JSON.stringify(allData, null, 4)); console.log('数据已保存,程序退出'); process.exit(); } // 绑定SIGINT回调 process.on('SIGINT', function onSigInt() { console.log('SIGINT ', new Date().toISOString()); shutdown(); }) // 非阻塞循环 async function loop() { while(true) { doCalculation(allData); await new Promise(resolve => setTimeout(resolve, 0)); } } await loop(); filesystem.writeFileSync('output.json', JSON.stringify(allData, null, 4)); } run();
方式3:类封装(适合复杂应用)
如果后续应用逻辑变得复杂,用类封装状态和行为会更清晰:
const filesystem = require('fs'); class CalculationApp { constructor() { this.allData = []; this.bindSignals(); } bindSignals() { process.on('SIGINT', () => { console.log('SIGINT ', new Date().toISOString()); this.shutdown(); }); } loadData() { try { const fileData = filesystem.readFileSync('output.json', {encoding:'utf-8', flag:'r'}); this.allData = JSON.parse(fileData.toString()); console.log(`Loaded ${this.allData.length} records from file`); } catch (err) { console.log('No file to load data from, continuing with empty dataset'); } } async runCalculationLoop() { while(true) { doCalculation(this.allData); await new Promise(resolve => setTimeout(resolve, 0)); } } shutdown() { filesystem.writeFileSync('output.json', JSON.stringify(this.allData, null, 4)); console.log('数据已保存,程序退出'); process.exit(); } async start() { this.loadData(); await this.runCalculationLoop(); this.shutdown(); } } // 启动应用 const app = new CalculationApp(); app.start();
关键注意点
- 必须避免同步无限循环:同步循环会彻底阻塞事件循环,导致SIGINT信号无法被处理。用
await new Promise(resolve => setTimeout(resolve, 0))可以让出事件循环,让Node有机会处理信号。 - 异步保存数据(可选):如果数据量很大,
writeFileSync会阻塞事件循环,这时可以改用异步写入,注意要等保存完成后再退出:
对应的SIGINT回调也要改为异步:async function shutdown() { await filesystem.promises.writeFile('output.json', JSON.stringify(allData, null, 4)); console.log('数据已保存,程序退出'); process.exit(); }process.on('SIGINT', async function onSigInt() { console.log('SIGINT ', new Date().toISOString()); await shutdown(); })
内容的提问来源于stack exchange,提问作者user2138149
相关产品推荐
相关产品推荐

