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 يسمح لك بتوجيه البيانات من ReadableStream في JavaScript مباشرة إلى إدخال العملية الفرعية:

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; // boolean — هل تم قتل العملية؟
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(`Max memory used: ${usage.maxRSS} bytes`);
console.log(`CPU time (user): ${usage.cpuTime.user} µs`);
console.log(`CPU time (system): ${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
// قتل العملية باستخدام 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("Respond to child");
  },
});

childProc.send("I am your father"); // يمكن للأصل إرسال رسائل إلى الابن أيضًا

في هذه الأثناء، يمكن للعملية الفرعية إرسال رسائل إلى أصلها باستخدام process.send() واستلام الرسائل مع process.on("message"). هذه هي نفس واجهة برمجة التطبيقات المستخدمة لـ child_process.fork() في Node.js.

ts
process.send("Hello from child as string");
process.send({ message: "Hello from child as object" });

process.on("message", message => {
  // طباعة الرسالة من الأصل
  console.log(message);
});
ts
// إرسال سلسلة
process.send("Hello from child as string");

// إرسال كائن
process.send({ message: "Hello from child as object" });

خيار serialization يتحكم في تنسيق الاتصال الأساسي بين العمليتين:

  • advanced: (افتراضي) يتم تسلسل الرسائل باستخدام واجهة برمجة تطبيقات JSC serialize، التي تدعم استنساخ كل ما يدعمه 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} 👋 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` });
  });
}

واجهة برمجة التطبيقات المحظورة (Bun.spawnSync())

يوفر Bun مرادفًا متزامنًا لـ Bun.spawn يسمى Bun.spawnSync. هذه هي واجهة برمجة التطبيقات المحظورة التي تدعم نفس المدخلات والمعلمات مثل 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 أفضل لخوادم 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 والأنواع. الأنواع الحقيقية تحتوي على generics معقدة لنوع تيارات 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 // equivalent to "ignore"
    | undefined // to use default
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // equivalent to "ignore"
    | undefined // to use default
    | 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 تحرير