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!"

輸出流 ​

你可以通過 stdout 和 stderr 屬性從子進程讀取結果。默認情況下,這些是 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.stringify 和 JSON.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. stdout 和 stderr 屬性是 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.spawn 和 Bun.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整理維護