Node.js Express中基于Socket.io的异步任务手动取消实现方案
解决方案:无需子进程实现异步任务的手动终止
核心思路
抛弃child_process创建独立进程的方案,改用AbortController(Node.js 15+原生支持)或自定义终止标志,在异步任务的关键执行节点检查终止信号,实现优雅中断,彻底避免额外的进程内存开销。
具体实现步骤
1. 维护任务终止控制器映射表
在服务器端全局维护一个Map,关联每个任务的uid与对应的终止控制器:
// 全局存储任务终止控制器,key为任务uid,value为AbortController实例 const taskControllers = new Map();
2. 修改任务启动逻辑
客户端触发tasks:launch时,创建AbortController并存储,将终止信号传入任务函数:
socket.on('tasks:launch', ({ uid, data }) => { const controller = new AbortController(); const signal = controller.signal; taskControllers.set(uid, controller); (async () => { try { await collector_obj.tasks.task(uid, data, io, signal); } catch (err) { // 捕获终止信号触发的错误,属于正常终止无需额外处理 if (err.message !== '任务已终止') { console.error(`任务 ${uid} 执行出错:`, err); } } finally { // 任务结束后清理控制器,避免内存泄漏 taskControllers.delete(uid); } })(); });
3. 改造任务函数支持终止
在任务的每个异步步骤检查终止信号,或使用支持AbortSignal的API:
task: async (uid, data, io, signal) => { const sleep = (ms, signal) => new Promise((resolve, reject) => { const timer = setTimeout(resolve, ms); // 监听终止信号,触发时清除定时器并抛出终止错误 signal.addEventListener('abort', () => { clearTimeout(timer); reject(new Error('任务已终止')); }); }); while (true) { // 每次循环前检查是否已触发终止 if (signal.aborted) { throw new Error('任务已终止'); } await sleep(1000, signal); console.log(Date.now()); // 数据库操作场景:优先用支持AbortSignal的驱动(如pg@8.8+、mongoose@6.8+) // 示例:PostgreSQL查询结合终止信号 // await pgClient.query('SELECT * FROM target_table', [], { signal }); // 若驱动不支持,手动在操作前后检查终止状态 // if (signal.aborted) throw new Error('任务已终止'); // await db.query(...); } }
4. 实现任务停止逻辑
客户端触发tasks:stop时,取出对应控制器并触发终止:
socket.on('tasks:stop', ({ uid }) => { const controller = taskControllers.get(uid); if (controller) { controller.abort(); console.log(`已发送终止信号给任务 ${uid}`); } else { console.log(`任务 ${uid} 不存在或已结束`); } });
注意事项
- 若Node.js版本低于15,可使用
events模块自定义终止事件,或用第三方库p-cancelable替代AbortController。 - 对于IO密集型操作(如数据库查询),优先选择支持
AbortSignal的驱动版本,能更高效中断正在进行的IO请求;若驱动不支持,需在操作前后手动检查终止标志。 - 任务无论正常结束还是被终止,都要从
taskControllers中删除对应控制器,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Wallo
相关产品推荐
相关产品推荐

