How to correctly handle child_process `close` event on try catch block? [duplicate] - node.js

I'm using the Bluebird promise library under Node.js, it's great! But I have a question:
If you take a look at the documentation of Node's child_process.exec and child_process.execFile you can see that both of these functions are returning a ChildProcess object.
So what's the recommended way to promisify such functions?
Note that the following works (I get a Promise object):
var Promise = require('bluebird');
var execAsync = Promise.promisify(require('child_process').exec);
var execFileAsync = Promise.promisify(require('child_process').execFile);
But how can one get access to the original return value of the original Node.js functions? (In these cases I would need to be able to access the originally returned ChildProcess objects.)
Any suggestion would be appreciated!
EDIT:
Here is an example code which is using the return value of the child_process.exec function:
var exec = require('child_process').exec;
var child = exec('node ./commands/server.js');
child.stdout.on('data', function(data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function(data) {
console.log('stderr: ' + data);
});
child.on('close', function(code) {
console.log('closing code: ' + code);
});
But if I would use the promisified version of the exec function ( execAsync from above ) then the return value will be a promise, not a ChildProcess object. This is the real problem I am talking about.

I would recommend using standard JS promises built into the language over an additional library dependency like Bluebird.
If you're using Node 10+, the Node.js docs recommend using util.promisify which returns a Promise<{ stdout, stderr }> object. See an example below:
const util = require('util');
const exec = util.promisify(require('child_process').exec);
async function lsExample() {
try {
const { stdout, stderr } = await exec('ls');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
} catch (e) {
console.error(e); // should contain code (exit code) and signal (that caused the termination).
}
}
lsExample()
Handle errors first from stderr.

It sounds like you'd like to return two things from the call:
the ChildProcess
a promise that resolves when the ChildProcess completes
So "the recommended way to promisify such functions"? Don't.
You're outside the convention. Promise returning functions are expected to return a promise, and that's it. You could return an object with two members (the ChildProcess & the promise), but that'll just confuse people.
I'd suggest calling the unpromisified function, and creating a promise based off the returned childProcess. (Maybe wrap that into a helper function)
This way, it's quite explicit for the next person who reads the code.
Something like:
var Promise = require('bluebird');
var exec = require('child_process').execFile;
function promiseFromChildProcess(child) {
return new Promise(function (resolve, reject) {
child.addListener("error", reject);
child.addListener("exit", resolve);
});
}
var child = exec('ls');
promiseFromChildProcess(child).then(function (result) {
console.log('promise complete: ' + result);
}, function (err) {
console.log('promise rejected: ' + err);
});
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('close', function (code) {
console.log('closing code: ' + code);
});
If you're just wanting to promisify specifically child_process.exec() and child_process.execFile(), in recent node versions there is a better answer here.

Since Node v12 the built-in util.promisify allows access to the ChildProcess object in the returned Promise for built-in functions where it would have been returned by the un-promisified call. From the docs:
The returned ChildProcess instance is attached to the Promise as a child property.
This correctly and simply satisfies the need to access ChildProcess in the original question and makes other answers out of date providing that Node v12+ can be used.
Adapting the example (and concise style) provided by the questioner, access to the ChildProcess can be achieved like:
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const promise = exec('node ./commands/server.js');
const child = promise.child;
child.stdout.on('data', function(data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function(data) {
console.log('stderr: ' + data);
});
child.on('close', function(code) {
console.log('closing code: ' + code);
});
// i.e. can then await for promisified exec call to complete
const { stdout, stderr } = await promise;

Here's another way:
function execPromise(command) {
return new Promise(function(resolve, reject) {
exec(command, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
resolve(stdout.trim());
});
});
}
Use the function:
execPromise(command).then(function(result) {
console.log(result);
}).catch(function(e) {
console.error(e.message);
});
Or with async/await:
try {
var result = await execPromise(command);
} catch (e) {
console.error(e.message);
}

There's probably not a way to do nicely that covers all use cases. But for limited cases, you can do something like this:
/**
* Promisified child_process.exec
*
* #param cmd
* #param opts See child_process.exec node docs
* #param {stream.Writable} opts.stdout If defined, child process stdout will be piped to it.
* #param {stream.Writable} opts.stderr If defined, child process stderr will be piped to it.
*
* #returns {Promise<{ stdout: string, stderr: stderr }>}
*/
function execp(cmd, opts) {
opts || (opts = {});
return new Promise((resolve, reject) => {
const child = exec(cmd, opts,
(err, stdout, stderr) => err ? reject(err) : resolve({
stdout: stdout,
stderr: stderr
}));
if (opts.stdout) {
child.stdout.pipe(opts.stdout);
}
if (opts.stderr) {
child.stderr.pipe(opts.stderr);
}
});
}
This accepts opts.stdout and opts.stderr arguments, so that stdio can be captured from the child process.
For example:
execp('ls ./', {
stdout: new stream.Writable({
write: (chunk, enc, next) => {
console.log(chunk.toString(enc));
next();
}
}),
stderr: new stream.Writable({
write: (chunk, enc, next) => {
console.error(chunk.toString(enc));
next();
}
})
}).then(() => console.log('done!'));
Or simply:
execp('ls ./', {
stdout: process.stdout,
stderr: process.stderr
}).then(() => console.log('done!'));

Just want to mention that there's a nice tool that will solve your problem completely:
https://www.npmjs.com/package/core-worker
This package makes it a lot easier to handle processes.
import { process } from "CoreWorker";
import fs from "fs";
const result = await process("node Server.js", "Server is ready.").ready(1000);
const result = await process("cp path/to/file /newLocation/newFile").death();
or combine these functions:
import { process } from "core-worker";
const simpleChat = process("node chat.js", "Chat ready");
setTimeout(() => simpleChat.kill(), 360000); // wait an hour and close the chat
simpleChat.ready(500)
.then(console.log.bind(console, "You are now able to send messages."))
.then(::simpleChat.death)
.then(console.log.bind(console, "Chat closed"))
.catch(() => /* handle err */);

Here are my two cents. Uses spawn which streams the output and writes to stdout and stderr. The error and standard output is captured in buffers and are returned or rejected.
This is written I Typescript, feel free to remove typings if using JavaScript:
import { spawn, SpawnOptionsWithoutStdio } from 'child_process'
const spawnAsync = async (
command: string,
options?: SpawnOptionsWithoutStdio
) =>
new Promise<Buffer>((resolve, reject) => {
const [spawnCommand, ...args] = command.split(/\s+/);
const spawnProcess = spawn(spawnCommand, args, options);
const chunks: Buffer[] = [];
const errorChunks: Buffer[] = [];
spawnProcess.stdout.on("data", (data) => {
process.stdout.write(data.toString());
chunks.push(data);
});
spawnProcess.stderr.on("data", (data) => {
process.stderr.write(data.toString());
errorChunks.push(data);
});
spawnProcess.on("error", (error) => {
reject(error);
});
spawnProcess.on("close", (code) => {
if (code === 1) {
reject(Buffer.concat(errorChunks).toString());
return;
}
resolve(Buffer.concat(chunks));
});
});

Just another example you might run into issues when running multiple commands when destructuring with the same const's you can rename them like this.
const util = require('util');
const exec = util.promisify(require('child_process').exec);
async function runCommands() {
try {
const { stdout, stderr } = await exec('ls');
console.log('stdout:', stdout);
console.log('stderr:', stderr);
const { stdout: stdoutTwo, stderr: stderrTwo } = await exec('ls');
console.log('stdoutTwo:', stdoutTwo);
console.log('stderrTwo:', stderrTwo);
const { stdout: stdoutThree, stderr: stderrThree } = await exec('ls');
console.log('stdoutThree:', stdoutThree);
console.log('stderrThree:', stderrThree);
} catch (e) {
console.error(e); // should contain code (exit code) and signal (that caused the termination).
}
}
runCommands()

Here's mine. It doesn't deal with stdin or stdout, so if you need those then use one of the other answers on this page. :)
// promisify `child_process`
// This is a very nice trick :-)
this.promiseFromChildProcess = function (child) {
return new Promise((resolve, reject) => {
child.addListener('error', (code, signal) => {
console.log('ChildProcess error', code, signal);
reject(code);
});
child.addListener('exit', (code, signal) => {
if (code === 0) {
resolve(code);
} else {
console.log('ChildProcess error', code, signal);
reject(code);
}
});
});
};

Related

lost promise in async await method

Hi guys, I'm newbie in node.js. I have function, that leads the dialogue with user in console. In newSwagger function I call the bumpDiff function, that shows changes between 2 files.
async function bumpDiff = () => {
exec('bump diff swagger.json newSwagger.json', (err, output) => {
// once the command has completed, the callback function is called
if (err) {
// log and return if we encounter an error
console.error("could not execute command: ", err)
return
}
// log the output received from the command
console.log("Output: \n", output)
})
};
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const question = (str) => new Promise(resolve => rl.question(str, resolve));
const steps = {
start: async () => {
return steps.changeSwagger();
},
changeSwagger: async () => {
const addSwagger = await request();
console.log('success');
const changeSwagger = await question("Would you like to check changes in swagger? Please type yes/no: ");
if (changeSwagger === 'yes') { return steps.newSwagger(); }
if (changeSwagger === 'no') { return steps.oldSwagger(); }
return steps.end();
},
newSwagger: async () => {
console.log('showing changes');
const diff = await bumpDiff();
const ask = await question('Would you like to change swagger? Please type yes/no: ');
if (ask === 'yes') { return steps.changing(); }
if (ask === 'no') { return steps.end(); }
},
changing: async () => {
const rebuildSwagger = await SwaggerNew();
console.log('swagger changed successfully');
return steps.end();
},
oldSwagger: async () => {
console.log('No worries, have a nice day');
return steps.end();
},
end: async () => {
rl.close();
},
};
steps.start();
The problems is: when bumpDiff is starting, next readline
'Would you like to change swagger? Please type yes/no: '
come faster, then changes appeares. I guess I'm missing something about promises, can you help me find the mistake please.
P.S. all other functions, like 'request' and 'SwaggerNew' are async.
You are mixing two "styles" the callback approach in the "exec" function and you are trying to await that bumpDiff that is not returning a Promise (async/await approach), take a look to utils.Promisify helper function.
From NodeJS docs:
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);
async function lsExample() {
const { stdout, stderr } = await exec('ls');
console.log('stdout:', stdout);
console.error('stderr:', stderr);
}
lsExample();
You need to turn the callback style exec call into a promise so that await bumpDiff() can work properly.
async function bumpDiff = () => {
return new Promise((resolve, reject) => {
exec('bump diff swagger.json newSwagger.json', (err, output) => {
if (err) {
console.error("could not execute command: ", err)
reject(err)
} else {
console.log("Output: \n", output)
resolve(output)
}
})
})
};

How to use promisify() with the spawn() function for the 'child_process'?

I have the following code example, and I have now ideas on how to resolve this using utils.promisify(); ONLY! Not Promise!
const spawn = child_process.spawn('docker', ['--version']);
spawn.stdout.on('data', (data) => {
process.stdout.write(data);
});
spawn.on('error', () => {
process.stderr.write(error);
process.exit(1);
});
The second code example works fine.
const promisifiedExecFile = promisify(child_process.execFile);
async function test() {
const version = await promisifiedExecFile('docker', ['--version']);
console.log(version);
}
test();
I couldn't quickly find out why the promisify function does now work properly with the spawn function. But you can create your own promisify function like this:
TS
import { spawn as spwn } from 'child_process';
const spawn = (
cmd: string,
args: ReadonlyArray<string>,
) => new Promise((resolve, reject) => {
const cp = spwn(cmd, args);
const error: string[] = [];
const stdout: string[] = [];
cp.stdout.on('data', (data) => {
stdout.push(data.toString());
});
cp.on('error', (e) => {
error.push(e.toString());
});
cp.on('close', () => {
if (error.length) reject(error.join(''));
else resolve(stdout.join(''));
});
});
(async () => {
try {
const stdOut = await spawn('docker', ['--version']);
console.log('stdOut: ', stdOut);
} catch (error) {
console.log('error:', error);
process.exit(1);
}
})();
JS
const { spawn: spwn } = require('child_process');
const spawn = (
cmd,
args,
) => new Promise((resolve, reject) => {
const cp = spwn(cmd, args);
const error = [];
const stdout = [];
cp.stdout.on('data', (data) => {
stdout.push(data.toString());
});
cp.on('error', (e) => {
error.push(e.toString());
});
cp.on('close', () => {
if (error.length) reject(error.join(''));
else resolve(stdout.join(''));
});
});
(async () => {
try {
const stdOut = await spawn('docker', ['--version']);
console.log('stdOut: ', stdOut);
} catch (error) {
console.log('error: ', error);
process.exit(1);
}
})();
Node.js' built-in util package has a promisify() function that converts callback-based functions to promise-based functions. This lets you use promise chaining and async/await with callback-based APIs.
I think that we can't use the promisify() with the spawn() function.
For example we can use promisify() with execFile() instead of spawn():
async asyncExecFile(tool) {
const execFile = util.promisify(child_process.execFile);
return await execFile(tool, ['--version'])
.catch(() => {
this.printError(`The "${tool}" don't exist in the current environment. \n`);
process.exit(0);
});
}
It is not possible because there is nothing to promisify. promisify works on functions where it takes a callback and spawn does not take a callback.
You use spawn by taking the returned ChildProcess then adding listeners to the ChildProcess' readable streams (stdout, stderr, stdio...)
Omar Omeiri's answer is similar to how execFile works inside node itself, so you can just use the promisified execFile instead. (if you need unlimited buffer, pass in maxBuffer: Infinity inside options)

How to create a file stream and write to the stream asynchronously?

I am new to TypeScript/JavaScript and Node.
Now I am trying to create a file stream and write "Hello!" to the stream asynchronously.
#!/usr/bin/env node
import fs from 'fs';
function createStream(filePath: string): Promise<fs.WriteStream> {
return new Promise<fs.WriteStream>((resolve, reject) => {
const out = fs.createWriteStream(filePath);
out.on('close', () => {
console.log(filePath + ' closed');
resolve(out);
});
out.on('error', (err: any) => {
console.log(filePath + ' ' + err);
reject(err);
});
});
}
createStream('/tmp/test.txt').then((out:fs.WriteStream) => {
console.log(out);
out.write('Hello!');
out.end();
})
This code does create /tmp/test.txt but prints out nothing and the file is empty.
What is the problem with this code ?
You don't need to resolve a promise with the fs.WriteStream since its creation is synchronous. Just call fs.createWriteStream() directly and pass the instance to your function to create a promise that settles when the stream closes or errors:
#!/usr/bin/env node
import fs from 'fs';
import stream from 'stream';
function promisify(s: stream.Stream): Promise<void> {
return new Promise<void>((resolve, reject) => {
const onClose = () => {
s.off('error', onError);
resolve();
};
const onError = (error: Error) => {
s.off('close', onClose);
reject(error);
};
s.once('close', onClose);
s.once('error', onError);
});
}
const out = fs.createWriteStream('/tmp/test.txt');
promisify(out).then(() => {
console.log('Done');
});
out.write('Hello!');
out.end();

Make exec wait for previous exec [duplicate]

I like to integrate exec from nodejs in a custom function to handle all the errors in this one function.
const exec = require('child_process').exec;
function os_func() {
this.execCommand = function(cmd) {
var ret;
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
ret = stdout;
});
return ret;
}
}
var os = new os_func();
This function returns undefined because exec isn't finished when the value returns.
How can i solve that? Can i force the function to wait for exec?
you can use promise as :
const exec = require('child_process').exec;
function os_func() {
this.execCommand = function (cmd) {
return new Promise((resolve, reject)=> {
exec(cmd, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
resolve(stdout)
});
})
}
}
var os = new os_func();
os.execCommand('pwd').then(res=> {
console.log("os >>>", res);
}).catch(err=> {
console.log("os >>>", err);
})
Since the command is executed asynchronously you will want to use a callback to handle the return value once the command has finished executing:
const exec = require('child_process').exec;
function os_func() {
this.execCommand = function(cmd, callback) {
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
callback(stdout);
});
}
}
var os = new os_func();
os.execCommand('SomeCommand', function (returnvalue) {
// Here you can get the return value
});
Yet another solution using ES6 modules:
import fs from "node:fs";
import {exec} from "node:child_process";
import util from "node:util";
// promisify exec
const execPromise = util.promisify(exec);
try {
// wait for exec to complete
const {stdout, stderr} = await execPromise("ls -l");
} catch (error) {
console.log(error);
}
exec will deal with it in an async fashion, so you should receive a callback or return a promise.
One thing you could do in order to make it sync is to use execSync instead:
https://nodejs.org/api/child_process.html#child_process_child_process_execsync_command_options
The child_process.execSync() method is generally identical to
child_process.exec() with the exception that the method will not
return until the child process has fully closed. When a timeout has
been encountered and killSignal is sent, the method won't return until
the process has completely exited. Note that if the child process
intercepts and handles the SIGTERM signal and doesn't exit, the parent
process will wait until the child process has exited.
Adding what worked for me, as none of the above did the trick!
const { exec } = require("child_process");
const util = require("util");
const execPromise = util.promisify(exec);
function parentFunction() {
...
// Trigger 'exec', then a-wait for it to finish
await execWrapper('<your-command-here>');
...
}
...
async function execWrapper(cmd) {
const { stdout, stderr } = await execPromise(cmd);
if (stdout) {
console.log(`stderr: ${stdout}`);
}
if (stderr) {
console.log(`stderr: ${stderr}`);
}
}
NOTE: This isn't your example, but just a generic one; for me - the cmd was a Docker build command. You could probably have execWrapper return back the stdout if needed.
You can do it with callback. Maybe you can try something like this:
function os_func() {
this.execCommand = function(cmd, myCallback) {
var ret;
exec(cmd, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
ret = stdout;
myCallback(ret);
});
}
function myCallback(ret){
// TODO: your stuff with return value...
}

child_process.spawn doesn't emit any events

I'm trying to run ripgrep from my Node app and am seeing a strange behavior with child_process.spawn: none of the events fire and the app never finishes (is stuck somewhere inside the spawn call):
import { spawn } from 'child_process';
async function run() {
await spawnWrapper('rg', ['-F', '"demo"'], { cwd: __dirname });
}
export function spawnWrapper(command, args, options) {
return new Promise((resolve, reject) => {
let stdout = '';
let stderr = '';
const child = spawn(command, args, options);
console.log('spawn wrapper');
child.on('close', (code, signal) => {
console.log('close');
resolve({ code, signal, stdout, stderr });
});
child.on('error', (error) => {
console.log('error');
(error as any).stderr = stderr;
reject(error);
});
child.on('exit', (code, signal) => {
console.log('exit');
resolve({ code, signal, stdout, stderr });
});
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (data) => {
console.log('stdout data');
stdout += data;
});
child.stderr.on('data', (data) => {
console.log('stderr data');
stderr += data;
});
});
}
I only get "spawn wrapper" in the console, no other events. I've never seen this behavior with other binaries, maybe it's something with ripgrep but still, shouldn't I be getting at least some hints by Node? Any suggestions on how to debug this?
It was caused by ripgrep waiting for input which was not obvious to me (on command line, it just executes straight away). Details here: https://github.com/BurntSushi/ripgrep/issues/410

Resources