Skip to content

生成进程 (Bun.spawn())

提供命令作为字符串数组。Bun.spawn() 的结果是 Bun.Subprocess 对象。

ts
const proc = Bun.spawn(["bun", "--version"]);
console.log(await proc.exited); // 0

Bun.spawn 的第二个参数是参数对象,可用于配置子进程。

ts
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // 指定工作目录
  env: { ...process.env, FOO: "bar" }, // 指定环境变量
  onExit(proc, exitCode, signalCode, error) {
    // 退出处理程序
  },
});

proc.pid; // 子进程的进程 ID

输入流

默认情况下,子进程的输入流是未定义的;可以使用 stdin 参数进行配置。

ts
const proc = Bun.spawn(["cat"], {
  stdin: await fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js"),
});

const text = await proc.stdout.text();
console.log(text); // "const input = "hello world".repeat(400); ..."
描述
null默认。 不提供输入给子进程
"pipe"返回 FileSink 用于快速增量写入
"inherit"继承父进程的 stdin
Bun.file()从指定文件读取
TypedArray | DataView使用二进制缓冲区作为输入
Response使用响应的 body 作为输入
Request使用请求的 body 作为输入
ReadableStream使用可读流作为输入
Blob使用 blob 作为输入
number从具有给定文件描述符的文件读取

"pipe" 选项允许从父进程增量写入子进程的输入流。

ts
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // 返回 FileSink 用于写入
});

// 入队字符串数据
proc.stdin.write("hello");

// 入队二进制数据
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// 发送缓冲数据
proc.stdin.flush();

// 关闭输入流
proc.stdin.end();

ReadableStream 传递给 stdin 允许你将数据从 JavaScript ReadableStream 直接管道传输到子进程的输入:

ts
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello from ");
    controller.enqueue("ReadableStream!");
    controller.close();
  },
});

const proc = Bun.spawn(["cat"], {
  stdin: stream,
  stdout: "pipe",
});

const output = await proc.stdout.text();
console.log(output); // "Hello from ReadableStream!"

输出流

你可以通过 stdoutstderr 属性从子进程读取结果。默认情况下,这些是 ReadableStream 实例。

ts
const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.3.3\n"

通过将以下值之一传递给 stdout/stderr 来配置输出流:

描述
"pipe"stdout 的默认值。 将输出管道传输到返回的 Subprocess 对象上的 ReadableStream
"inherit"stderr 的默认值。 从父进程继承
"ignore"丢弃输出
Bun.file()写入指定文件
number写入具有给定文件描述符的文件

退出处理

使用 onExit 回调监听进程退出或被终止。

ts
const proc = Bun.spawn(["bun", "--version"], {
  onExit(proc, exitCode, signalCode, error) {
    // 退出处理程序
  },
});

为方便起见,exited 属性是一个 Promise,在进程退出时解析。

ts
const proc = Bun.spawn(["bun", "--version"]);

await proc.exited; // 在进程退出时解析
proc.killed; // 布尔值 — 进程是否被终止?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...

要终止进程:

ts
const proc = Bun.spawn(["bun", "--version"]);
proc.kill();
proc.killed; // true

proc.kill(15); // 指定信号代码
proc.kill("SIGTERM"); // 指定信号名称

bun 进程在所有子进程退出之前不会终止。使用 proc.unref() 将子进程从父进程分离。

ts
const proc = Bun.spawn(["bun", "--version"]);
proc.unref();

资源使用

你可以在进程退出后获取有关进程资源使用的信息:

ts
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited;

const usage = proc.resourceUsage();
console.log(`最大内存使用:${usage.maxRSS} 字节`);
console.log(`CPU 时间(用户):${usage.cpuTime.user} µs`);
console.log(`CPU 时间(系统):${usage.cpuTime.system} µs`);

使用 AbortSignal

你可以使用 AbortSignal 中止子进程:

ts
const controller = new AbortController();
const { signal } = controller;

const proc = Bun.spawn({
  cmd: ["sleep", "100"],
  signal,
});

// 稍后,中止进程:
controller.abort();

使用 timeout 和 killSignal

你可以为子进程设置超时,使其在特定持续时间后自动终止:

ts
// 5 秒后终止进程
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 5 秒(毫秒)
});

await proc.exited; // 将在 5 秒后解析

默认情况下,超时的进程使用 SIGTERM 信号终止。你可以使用 killSignal 选项指定不同的信号:

ts
// 5 秒后使用 SIGKILL 终止进程
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // 可以是字符串名称或信号编号
});

killSignal 选项还控制在 AbortSignal 被中止时发送哪个信号。

使用 maxBuffer

对于 spawnSync,你可以限制输出的最大字节数,超过后进程将被终止:

ts
// 在 'yes' 发出超过 100 字节输出后终止
const result = Bun.spawnSync({
  cmd: ["yes"], // 或在 Windows 上使用 ["bun", "exec", "yes"]
  maxBuffer: 100,
});
// 进程退出

进程间通信 (IPC)

Bun 支持两个 bun 进程之间的直接进程间通信通道。要从生成的 Bun 子进程接收消息,指定 ipc 处理程序。

ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * 从子进程接收的消息
     **/
  },
});

父进程可以使用返回的 Subprocess 实例上的 .send() 方法向子进程发送消息。发送子进程的引用也可在 ipc 处理程序中作为第二个参数使用。

ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * 从子进程接收的消息
     **/
    childProc.send("响应子进程");
  },
});

childProc.send("我是你的父进程"); // 父进程也可以向子进程发送消息

同时,子进程可以使用 process.send() 向父进程发送消息,并使用 process.on("message") 接收消息。这与 Node.js 中 child_process.fork() 使用的 API 相同。

ts
process.send("来自子进程的字符串消息");
process.send({ message: "来自子进程的对象消息" });

process.on("message", message => {
  // 打印来自父进程的消息
  console.log(message);
});
ts
// 发送字符串
process.send("来自子进程的字符串消息");

// 发送对象
process.send({ message: "来自子进程的对象消息" });

serialization 选项控制两个进程之间的底层通信格式:

  • advanced:(默认)消息使用 JSC serialize API 序列化,支持克隆 structuredClone 支持的所有内容。这不支持传输对象的所有权。
  • json:消息使用 JSON.stringifyJSON.parse 序列化,不支持 advanced 支持的那么多对象类型。

要从父进程断开 IPC 通道,调用:

ts
childProc.disconnect();

Bun 和 Node.js 之间的 IPC

要在 bun 进程和 Node.js 进程之间使用 IPC,在 Bun.spawn 中设置 serialization: "json"。这是因为 Node.js 和 Bun 使用具有不同对象序列化格式的不同 JavaScript 引擎。

js
if (typeof Bun !== "undefined") {
  const prefix = `[bun ${process.versions.bun} 🐇]`;
  const node = Bun.spawn({
    cmd: ["node", __filename],
    ipc({ message }) {
      console.log(message);
      node.send({ message: `${prefix} 👋 hey node` });
      node.kill();
    },
    stdio: ["inherit", "inherit", "inherit"],
    serialization: "json",
  });

  node.send({ message: `${prefix} 👋 hey node` });
} else {
  const prefix = `[node ${process.version}]`;
  process.on("message", ({ message }) => {
    console.log(message);
    process.send({ message: `${prefix} 👋 hey bun` });
  });
}

阻塞 API (Bun.spawnSync())

Bun 提供 Bun.spawn 的同步等效项 Bun.spawnSync。这是一个阻塞 API,支持与 Bun.spawn 相同的输入和参数。它返回 SyncSubprocess 对象,与 Subprocess 在一些方面有所不同。

  1. 它包含 success 属性,指示进程是否以零退出代码退出。
  2. stdoutstderr 属性是 Buffer 实例而不是 ReadableStream
  3. 没有 stdin 属性。使用 Bun.spawn 增量写入子进程的输入流。
ts
const proc = Bun.spawnSync(["echo", "hello"]);

console.log(proc.stdout.toString());
// => "hello\n"

根据经验法则,异步 Bun.spawn API 更适合 HTTP 服务器和应用程序,而 Bun.spawnSync 更适合构建命令行工具。


基准测试

NOTE

⚡️ 在底层,`Bun.spawn` 和 `Bun.spawnSync` 使用 [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html)。

Bun 的 spawnSync 生成进程比 Node.js child_process 模块快 60%。

bash
bun spawn.mjs
txt
cpu: Apple M1 Max
runtime: bun 1.x (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi  888.14 µs/iter    (821.83 µs … 1.2 ms) 905.92 µs      1 ms   1.03 ms
sh
node spawn.node.mjs
txt
cpu: Apple M1 Max
runtime: node v18.9.1 (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi    1.47 ms/iter     (1.14 ms … 2.64 ms)   1.57 ms   2.37 ms   2.52 ms

参考

下面是 Spawn API 和类型的参考。实际类型具有复杂的泛型,用于使用传递给 Bun.spawnBun.spawnSync 的选项强类型化 Subprocess 流。有关完整详细信息,请在 bun.d.ts 中找到这些类型定义。

ts
interface Bun {
  spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
  spawnSync(command: string[], options?: SpawnOptions.OptionsObject): SyncSubprocess;

  spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
  spawnSync(options: { cmd: string[] } & SpawnOptions.OptionsObject): SyncSubprocess;
}

namespace SpawnOptions {
  interface OptionsObject {
    cwd?: string;
    env?: Record<string, string | undefined>;
    stdio?: [Writable, Readable, Readable];
    stdin?: Writable;
    stdout?: Readable;
    stderr?: Readable;
    onExit?(
      subprocess: Subprocess,
      exitCode: number | null,
      signalCode: number | null,
      error?: ErrorLike,
    ): void | Promise<void>;
    ipc?(message: any, subprocess: Subprocess): void;
    serialization?: "json" | "advanced";
    windowsHide?: boolean;
    windowsVerbatimArguments?: boolean;
    argv0?: string;
    signal?: AbortSignal;
    timeout?: number;
    killSignal?: string | number;
    maxBuffer?: number;
  }

  type Readable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // 等同于 "ignore"
    | undefined // 使用默认值
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // 等同于 "ignore"
    | undefined // 使用默认值
    | BunFile
    | ArrayBufferView
    | number
    | ReadableStream
    | Blob
    | Response
    | Request;
}

interface Subprocess extends AsyncDisposable {
  readonly stdin: FileSink | number | undefined;
  readonly stdout: ReadableStream<Uint8Array> | number | undefined;
  readonly stderr: ReadableStream<Uint8Array> | number | undefined;
  readonly readable: ReadableStream<Uint8Array> | number | undefined;
  readonly pid: number;
  readonly exited: Promise<number>;
  readonly exitCode: number | null;
  readonly signalCode: NodeJS.Signals | null;
  readonly killed: boolean;

  kill(exitCode?: number | NodeJS.Signals): void;
  ref(): void;
  unref(): void;

  send(message: any): void;
  disconnect(): void;
  resourceUsage(): ResourceUsage | undefined;
}

interface SyncSubprocess {
  stdout: Buffer | undefined;
  stderr: Buffer | undefined;
  exitCode: number;
  success: boolean;
  resourceUsage: ResourceUsage;
  signalCode?: string;
  exitedDueToTimeout?: true;
  pid: number;
}

interface ResourceUsage {
  contextSwitches: {
    voluntary: number;
    involuntary: number;
  };

  cpuTime: {
    user: number;
    system: number;
    total: number;
  };
  maxRSS: number;

  messages: {
    sent: number;
    received: number;
  };
  ops: {
    in: number;
    out: number;
  };
  shmSize: number;
  signalCount: number;
  swapCount: number;
}

type Signal =
  | "SIGABRT"
  | "SIGALRM"
  | "SIGBUS"
  | "SIGCHLD"
  | "SIGCONT"
  | "SIGFPE"
  | "SIGHUP"
  | "SIGILL"
  | "SIGINT"
  | "SIGIO"
  | "SIGIOT"
  | "SIGKILL"
  | "SIGPIPE"
  | "SIGPOLL"
  | "SIGPROF"
  | "SIGPWR"
  | "SIGQUIT"
  | "SIGSEGV"
  | "SIGSTKFLT"
  | "SIGSTOP"
  | "SIGSYS"
  | "SIGTERM"
  | "SIGTRAP"
  | "SIGTSTP"
  | "SIGTTIN"
  | "SIGTTOU"
  | "SIGUNUSED"
  | "SIGURG"
  | "SIGUSR1"
  | "SIGUSR2"
  | "SIGVTALRM"
  | "SIGWINCH"
  | "SIGXCPU"
  | "SIGXFSZ"
  | "SIGBREAK"
  | "SIGLOST"
  | "SIGINFO";

Bun学习网由www.bunjs.com.cn整理维护