Node.js子进程:如何在子进程间同步AbortController
问题
我需要启动两个通过download_sections.sh脚本执行请求的子进程,要求当其中一个子进程失败时,能够终止另一个子进程。但我编写的代码未实现预期效果:当音频的YouTube链接存在错误,导致对应子进程以代码1退出并触发中止操作后,视频子进程仍持续运行。
export const handler = async () => { const sections = "*0:00:00-0:00:30,*0:01:00-0:01:30"; const abortController = new AbortController(); const fetchOriginalVideo = `./scripts/download_sections.sh https://www.youtube.com/watch?v=HGl75kurxok ${sections} video`; const fetchAuxiliaryAudio = `./scripts/download_sections.sh https://www.youtube.com/watch?v= ${sections} audio`; try { const [video, audio] = await Promise.all([ runInParallel(fetchOriginalVideo, abortController), runInParallel(fetchAuxiliaryAudio, abortController), ]); } catch (err) { abortController.abort(); console.error("Error during parallel execution:", err); } finally { console.log("Handler execution completed."); } }; const runInParallel = (cmd: string, abortController: AbortController) => new Promise((resolve, reject) => { const [command, ...argumentList] = cmd.split(" "); console.log(command); console.log(argumentList); let child = spawn(command, argumentList, { signal: abortController.signal, }); child.stdout.on("data", (data) => { console.log(`stdout: ${data}`); }); child.stderr.on("data", (data) => { console.log(`stderr: ${data}`); }); child.on("close", (code) => { if (code === 0) resolve({}); else { abortController.abort(); reject(new Error(`Subprocess exited with code ${code}`)); } }); child.on("error", (error) => { abortController.abort(); reject(error); }); });
解决方案
你的代码存在两个核心问题,导致子进程无法被正确终止:
1. 命令拆分逻辑错误
用cmd.split(" ")拆分命令会把包含特殊格式的参数(比如你定义的sections时间范围)拆成多个无效片段,导致spawn执行的命令参数完全错误,同时关联的中止信号也无法正确绑定到子进程。
修复方式:放弃字符串拼接命令的方式,直接传递命令和参数数组:
// 修改handler里的参数定义 const command = "./scripts/download_sections.sh"; const fetchOriginalVideoArgs = ["https://www.youtube.com/watch?v=HGl75kurxok", sections, "video"]; const fetchAuxiliaryAudioArgs = ["https://www.youtube.com/watch?v=", sections, "audio"]; // 同时调整runInParallel函数的参数接收方式 const runInParallel = (command: string, args: string[], abortController: AbortController) => ...
2. AbortSignal无法自动终止已启动的子进程
Node.js中spawn选项里的signal仅在子进程未启动成功前生效。如果子进程已经开始运行,调用abortController.abort()不会自动杀死它,必须手动监听abort事件并调用child.kill()。
修复方式:在runInParallel中添加中止事件监听,主动杀死子进程:
const runInParallel = (command: string, args: string[], abortController: AbortController) => new Promise((resolve, reject) => { const child = spawn(command, args); // 监听中止信号,手动杀死子进程 const abortHandler = () => { if (!child.killed) { child.kill(); // 可指定信号,比如child.kill("SIGKILL")强制终止 } }; abortController.signal.addEventListener('abort', abortHandler); child.stdout.on("data", (data) => { console.log(`stdout: ${data}`); }); child.stderr.on("data", (data) => { console.log(`stderr: ${data}`); }); child.on("close", (code) => { // 移除监听避免内存泄漏 abortController.signal.removeEventListener('abort', abortHandler); if (code === 0) resolve({}); else { abortController.abort(); reject(new Error(`Subprocess exited with code ${code}`)); } }); child.on("error", (error) => { abortController.signal.removeEventListener('abort', abortHandler); abortController.abort(); reject(error); }); });
最终调整后的handler
export const handler = async () => { const sections = "*0:00:00-0:00:30,*0:01:00-0:01:30"; const abortController = new AbortController(); const command = "./scripts/download_sections.sh"; const fetchOriginalVideoArgs = ["https://www.youtube.com/watch?v=HGl75kurxok", sections, "video"]; const fetchAuxiliaryAudioArgs = ["https://www.youtube.com/watch?v=", sections, "audio"]; try { const [video, audio] = await Promise.all([ runInParallel(command, fetchOriginalVideoArgs, abortController), runInParallel(command, fetchAuxiliaryAudioArgs, abortController), ]); } catch (err) { abortController.abort(); console.error("Error during parallel execution:", err); } finally { console.log("Handler execution completed."); } };
内容的提问来源于stack exchange,提问作者Netlemon Opp
相关产品推荐
相关产品推荐

