execa logo
# 📞 Inter-process communication ## Exchanging messages When the [`ipc`](api.md#optionsipc) option is `true`, the current process and subprocess can exchange messages. This only works if the subprocess is a [Node.js file](node.md). The `ipc` option defaults to `true` when using [`execaNode()`](node.md#run-nodejs-files) or the [`node`](node.md#run-nodejs-files) option. Please note that unlike `node:child_process` [`spawn()`](https://nodejs.org/api/child_process.html#optionsstdio), the [`stdio` option](api.md#optionsstdio) does not support the `'ipc'` value. Instead, the [`ipc`](api.md#optionsipc) option must be set to `true`. The current process sends messages with [`subprocess.sendMessage(message)`](api.md#subprocesssendmessagemessage-sendmessageoptions) and receives them with [`subprocess.getOneMessage()`](api.md#subprocessgetonemessagegetonemessageoptions). The subprocess uses [`sendMessage(message)`](api.md#sendmessagemessage-sendmessageoptions) and [`getOneMessage()`](api.md#getonemessagegetonemessageoptions). Those are the same methods, but imported directly from the `'execa'` module. ```js // parent.js import {execaNode} from 'execa'; const subprocess = execaNode`child.js`; await subprocess.sendMessage('Hello from parent'); const message = await subprocess.getOneMessage(); console.log(message); // 'Hello from child' await subprocess; ``` ```js // child.js import {getOneMessage, sendMessage} from 'execa'; const message = await getOneMessage(); // 'Hello from parent' const newMessage = message.replace('parent', 'child'); // 'Hello from child' await sendMessage(newMessage); ``` ## Listening to messages The methods described above read a single message. On the other hand, [`subprocess.getEachMessage()`](api.md#subprocessgeteachmessagegeteachmessageoptions) and [`getEachMessage()`](api.md#geteachmessagegeteachmessageoptions) return an [async iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_async_iterator_and_async_iterable_protocols). This should be preferred when listening to multiple messages. [`subprocess.getEachMessage()`](api.md#subprocessgeteachmessagegeteachmessageoptions) waits for the subprocess to end (even when using [`break`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break) or [`return`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return)). It throws if the subprocess [fails](api.md#result). This means you do not need to `await` the subprocess' [promise](execution.md#result). ```js // parent.js import {execaNode} from 'execa'; const subprocess = execaNode`child.js`; await subprocess.sendMessage(0); // This loop ends when the subprocess exits. // It throws if the subprocess fails. for await (const message of subprocess.getEachMessage()) { console.log(message); // 1, 3, 5, 7, 9 await subprocess.sendMessage(message + 1); } ``` ```js // child.js import {sendMessage, getEachMessage} from 'execa'; // The subprocess exits when hitting `break` for await (const message of getEachMessage()) { if (message === 10) { break; } console.log(message); // 0, 2, 4, 6, 8 await sendMessage(message + 1); } ``` ## Filter messages ```js import {getOneMessage} from 'execa'; const startMessage = await getOneMessage({ filter: message => message.type === 'start', }); ``` ```js import {getEachMessage} from 'execa'; for await (const message of getEachMessage()) { if (message.type === 'start') { // ... } } ``` ## Ensure messages are received When a message is sent by one process, the other process must receive it using [`getOneMessage()`](#exchanging-messages), [`getEachMessage()`](#listening-to-messages), or automatically with [`result.ipcOutput`](api.md#resultipcoutput). If not, that message is silently discarded. If the [`strict: true`](api.md#sendmessageoptionsstrict) option is passed to [`subprocess.sendMessage(message)`](api.md#subprocesssendmessagemessage-sendmessageoptions) or [`sendMessage(message)`](api.md#sendmessagemessage-sendmessageoptions), an error is thrown instead. This helps identifying subtle race conditions like the following example. ```js // main.js import {execaNode} from 'execa'; const subprocess = execaNode`build.js`; // This `build` message is received await subprocess.sendMessage('build', {strict: true}); // This `lint` message is not received, so it throws await subprocess.sendMessage('lint', {strict: true}); await subprocess; ``` ```js // build.js import {getOneMessage} from 'execa'; // Receives the 'build' message const task = await getOneMessage(); // The `lint` message is sent while `runTask()` is ongoing // Therefore the `lint` message is discarded await runTask(task); // Does not receive the `lint` message // Without `strict`, this would wait forever const secondTask = await getOneMessage(); await runTask(secondTask); ``` ## Retrieve all messages The [`result.ipcOutput`](api.md#resultipcoutput) array contains all the messages sent by the subprocess. In many situations, this is simpler than using [`subprocess.getOneMessage()`](api.md#subprocessgetonemessagegetonemessageoptions) and [`subprocess.getEachMessage()`](api.md#subprocessgeteachmessagegeteachmessageoptions). ```js // main.js import {execaNode} from 'execa'; const {ipcOutput} = await execaNode`build.js`; console.log(ipcOutput[0]); // {kind: 'start', timestamp: date} console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date} ``` ```js // build.js import {sendMessage} from 'execa'; await sendMessage({kind: 'start', timestamp: new Date()}); await runBuild(); await sendMessage({kind: 'stop', timestamp: new Date()}); ``` ## Send an initial message The [`ipcInput`](api.md#optionsipcinput) option sends a message to the [Node.js subprocess](node.md) when it starts. ```js // main.js import {execaNode} from 'execa'; const ipcInput = [ {task: 'lint', ignore: /test\.js/}, {task: 'copy', files: new Set(['main.js', 'index.js']), }]; await execaNode({ipcInput})`build.js`; ``` ```js // build.js import {getOneMessage} from 'execa'; const ipcInput = await getOneMessage(); ``` ## Message type By default, messages are serialized using [`structuredClone()`](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). This supports most types including objects, arrays, `Error`, `Date`, `RegExp`, `Map`, `Set`, `bigint`, `Uint8Array`, and circular references. This throws when passing functions, symbols or promises (including inside an object or array). To limit messages to JSON instead, the [`serialization`](api.md#optionsserialization) option can be set to `'json'`. ```js import {execaNode} from 'execa'; await execaNode({serialization: 'json'})`child.js`; ``` ## Messages order The messages are always received in the same order they were sent. Even when sent all at once. ```js import {sendMessage} from 'execa'; await Promise.all([ sendMessage('first'), sendMessage('second'), sendMessage('third'), ]); ``` ## Keeping the subprocess alive By default, the subprocess is kept alive as long as [`getOneMessage()`](api.md#getonemessagegetonemessageoptions) or [`getEachMessage()`](api.md#geteachmessagegeteachmessageoptions) is waiting. This is recommended if you're sure the current process will send a message, as this prevents the subprocess from exiting too early. However, if you don't know whether a message will be sent, this can leave the subprocess hanging forever. In that case, the [`reference: false`](api.md#geteachmessageoptionsreference) option can be set. ```js import {getEachMessage} from 'execa'; // {type: 'gracefulExit'} is sometimes received, but not always for await (const message of getEachMessage({reference: false})) { if (message.type === 'gracefulExit') { gracefulExit(); } } ``` ## Debugging When the [`verbose`](api.md#optionsverbose) option is `'full'`, the IPC messages sent by the subprocess to the current process are [printed on the console](debugging.md#full-mode). Also, when the subprocess [failed](errors.md#subprocess-failure), [`error.ipcOutput`](api.md) contains all the messages sent by the subprocess. Those are also shown at the end of the [error message](errors.md#error-message).
[**Next**: 🐛 Debugging](debugging.md)\ [**Previous**: ⏳️ Streams](streams.md)\ [**Top**: Table of contents](../readme.md#documentation)