Note: I already found a solution to this problem, posting it here for posterity. See the selected answer.
The following (simplified) code throws an uncatchable "write EPIPE" (and in some scenarios "write EOF") error:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
const p = exec("gibberishThatWillFailImmediately");
p.stdin.write(veryLargeString);
My failed attempts at the problem:
stdin.destroyed
flag before writingstdin.writeableEnded
flag before writingstdin.writeableEnded
before each chunk. This one lead to undeterministic behavior.stdin.write(data)
line with try-catchstdin.end(data)
instead of stdin.write(data)
stdin.write()
a callback that should get any error that occurs. The callback got the error, but didn't prevent it from being thrown.Registering an 'error' handler to the stdin
stream seems to prevent the error from being thrown. Like this:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
const p = exec("gibberishThatWillFailImmediately");
p.stdin.on('error', (error) => console.log("error caught: ", error));
p.stdin.write(veryLargeString);
Here's an example that returns a promise containing the error or null if no error occured:
const { exec } = require("child_process");
const veryLargeString = "x".repeat(10 * 1024 * 1024);
function safelyWriteDataToStdin(stdin, data) {
// Register an awaitable callback that will capture any error occuring during the write operation
const promise = new Promise((resolve, _reject) => {
// Using once() and not on() to remove the listener after the first catch.
stdin.once("error", (error) => resolve(error));
// stdin.end(data, callback) can probably be used here, but I keep the `write()` just in case `end()`'s callback is called before the 'error' event, since the docs are not clear about that. (docs say: "The callback is invoked before 'finish' or on error." for node version 15.0.0. Is "on error" how node people say "after error"? idk.)
stdin.write(
data,
(error) => {
if (!error) resolve(null); // The condition is necessary because when an error occurs, the callback is called before the 'error' event handler
} // Signal the promise to complete when the write operation is complete with no errors. I don't simply use this `error` parameter because the exception will still be thrown if I don't listen to the 'error' event, and the docs say: "If an error occurs, the callback may or may not be called with the error as its first argument. To reliably detect write errors, add a listener for the 'error' event.". Also, I tested it myself and got two different errors in this callback and in the 'error' event handler.
);
});
return promise;
}
const p = exec("gibberishThatWillFailImmediately");
safelyWriteDataToStdin(p.stdin, veryLargeString).then((error)=>console.log("The error is:", error ));