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; // идентификатор процесса подпроцесса

Входной поток

По умолчанию входной поток подпроцесса не определён; его можно настроить с помощью параметра 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. Направить вывод в ReadableStream возвращённого объекта Subprocess
"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} мкс`);
console.log(`Время CPU (система): ${usage.cpuTime.system} мкс`);

Использование 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
// Завершить процесс с помощью SIGKILL через 5 секунд
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // Может быть именем сигнала или номером сигнала
});

Опция killSignal также управляет тем, какой сигнал отправляется при прерывании AbortSignal.

Использование maxBuffer

Для spawnSync вы можете ограничить максимальное количество байтов вывода перед завершением процесса:

ts
// Завершить 'yes' после того, как он выдаст более 100 байт вывода
const result = Bun.spawnSync({
  cmd: ["yes"], // или ["bun", "exec", "yes"] на Windows
  maxBuffer: 100,
});
// процесс завершается

Межпроцессное взаимодействие (IPC)

Bun поддерживает прямой канал межпроцессного взаимодействия между двумя процессами bun. Для получения сообщений от порождённого подпроцесса Bun укажите обработчик ipc.

ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * Сообщение, полученное от подпроцесса
     **/
  },
});

Родительский процесс может отправлять сообщения подпроцессу с помощью метода .send() в возвращённом экземпляре Subprocess. Ссылка на отправляющий подпроцесс также доступна как второй аргумент в обработчике ipc.

ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * Сообщение, полученное от подпроцесса
     **/
    childProc.send("Ответ дочернему процессу");
  },
});

childProc.send("Я твой отец"); // Родитель также может отправлять сообщения дочернему процессу

Тем временем дочерний процесс может отправлять сообщения своему родителю с помощью process.send() и получать сообщения с помощью process.on("message"). Это тот же API, который используется для child_process.fork() в Node.js.

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();

IPC между Bun и Node.js

Для использования IPC между процессом bun и процессом Node.js установите serialization: "json" в Bun.spawn. Это связано с тем, что 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} 👋 привет node` });
      node.kill();
    },
    stdio: ["inherit", "inherit", "inherit"],
    serialization: "json",
  });

  node.send({ message: `${prefix} 👋 привет node` });
} else {
  const prefix = `[node ${process.version}]`;
  process.on("message", ({ message }) => {
    console.log(message);
    process.send({ message: `${prefix} 👋 привет 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"

Как правило, асинхронный API Bun.spawn лучше подходит для HTTP-серверов и приложений, а Bun.spawnSync лучше подходит для создания инструментов командной строки.


Бенчмарки

NOTE

⚡️ Под капотом `Bun.spawn` и `Bun.spawnSync` используют [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).

spawnSync от Bun запускает процессы на 60% быстрее, чем модуль child_process Node.js.

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 и типов показан ниже. Реальные типы имеют сложные дженерики для строгой типизации потоков Subprocess с опциями, переданными в Bun.spawn и Bun.spawnSync. Полную информацию см. в этих типах, определённых в 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