You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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));
}

核心问题分析

  1. 同步无限循环阻塞事件循环:while(true)是同步代码,会彻底卡住Node.js的事件循环,导致SIGINT信号的回调根本无法执行——Node需要等同步代码执行完毕,才会处理事件队列中的信号。
  2. 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会阻塞事件循环,这时可以改用异步写入,注意要等保存完成后再退出:
    async function shutdown() {
        await filesystem.promises.writeFile('output.json', JSON.stringify(allData, null, 4));
        console.log('数据已保存,程序退出');
        process.exit();
    }
    
    对应的SIGINT回调也要改为异步:
    process.on('SIGINT', async function onSigInt() {
        console.log('SIGINT ', new Date().toISOString());
        await shutdown();
    })
    

内容的提问来源于stack exchange,提问作者user2138149

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 06:20:41