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整理維護