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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:34:53