Skip to content

Commit d66d4fc

Browse files
giltayaraddaleax
authored andcommitted
child_process: promisify includes stdio in error
This converts the initial implementation of a promised exec that used the customPromisifyArgs support in util.promisify with a custom implementation. This is because exec and execFile, when there is an error, still supply the stdout and stderr of the process, and yet the promisified version with customPromisifyArgs does not supply this ability. I created a custom implementation and attached it to exec and execFile using the util.promisify.custom key. Fixes: #13364 PR-URL: #13388 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent bf06534 commit d66d4fc

File tree

3 files changed

+52
-9
lines changed

3 files changed

+52
-9
lines changed

doc/api/child_process.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ child runs longer than `timeout` milliseconds.
215215
replace the existing process and uses a shell to execute the command.
216216

217217
If this method is invoked as its [`util.promisify()`][]ed version, it returns
218-
a Promise for an object with `stdout` and `stderr` properties.
218+
a Promise for an object with `stdout` and `stderr` properties. In case of an
219+
error, a rejected promise is returned, with the same `error` object given in the
220+
callback, but with an additional two properties `stdout` and `stderr`.
219221

220222
For example:
221223

@@ -281,7 +283,9 @@ stderr output. If `encoding` is `'buffer'`, or an unrecognized character
281283
encoding, `Buffer` objects will be passed to the callback instead.
282284

283285
If this method is invoked as its [`util.promisify()`][]ed version, it returns
284-
a Promise for an object with `stdout` and `stderr` properties.
286+
a Promise for an object with `stdout` and `stderr` properties. In case of an
287+
error, a rejected promise is returned, with the same `error` object given in the
288+
callback, but with an additional two properties `stdout` and `stderr`.
285289

286290
```js
287291
const util = require('util');

lib/child_process.js

+27-7
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
'use strict';
2323

2424
const util = require('util');
25-
const {
26-
deprecate, convertToValidSignal, customPromisifyArgs
27-
} = require('internal/util');
25+
const { deprecate, convertToValidSignal } = require('internal/util');
26+
const { createPromise,
27+
promiseResolve, promiseReject } = process.binding('util');
2828
const debug = util.debuglog('child_process');
2929

3030
const uv = process.binding('uv');
@@ -140,9 +140,27 @@ exports.exec = function(command /*, options, callback*/) {
140140
opts.callback);
141141
};
142142

143-
Object.defineProperty(exports.exec, customPromisifyArgs,
144-
{ value: ['stdout', 'stderr'], enumerable: false });
143+
const customPromiseExecFunction = (orig) => {
144+
return (...args) => {
145+
const promise = createPromise();
145146

147+
orig(...args, (err, stdout, stderr) => {
148+
if (err !== null) {
149+
err.stdout = stdout;
150+
err.stderr = stderr;
151+
promiseReject(promise, err);
152+
} else {
153+
promiseResolve(promise, { stdout, stderr });
154+
}
155+
});
156+
return promise;
157+
};
158+
};
159+
160+
Object.defineProperty(exports.exec, util.promisify.custom, {
161+
enumerable: false,
162+
value: customPromiseExecFunction(exports.exec)
163+
});
146164

147165
exports.execFile = function(file /*, args, options, callback*/) {
148166
var args = [];
@@ -338,8 +356,10 @@ exports.execFile = function(file /*, args, options, callback*/) {
338356
return child;
339357
};
340358

341-
Object.defineProperty(exports.execFile, customPromisifyArgs,
342-
{ value: ['stdout', 'stderr'], enumerable: false });
359+
Object.defineProperty(exports.execFile, util.promisify.custom, {
360+
enumerable: false,
361+
value: customPromiseExecFunction(exports.execFile)
362+
});
343363

344364
const _deprecatedCustomFds = deprecate(
345365
function deprecateCustomFds(options) {

test/parallel/test-child-process-promisified.js

+19
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,22 @@ const execFile = promisify(child_process.execFile);
3232
assert(err.message.includes('doesntexist'));
3333
}));
3434
}
35+
const failingCodeWithStdoutErr =
36+
'console.log(42);console.error(43);process.exit(1)';
37+
{
38+
exec(`${process.execPath} -e "${failingCodeWithStdoutErr}"`)
39+
.catch(common.mustCall((err) => {
40+
assert.strictEqual(err.code, 1);
41+
assert.strictEqual(err.stdout, '42\n');
42+
assert.strictEqual(err.stderr, '43\n');
43+
}));
44+
}
45+
46+
{
47+
execFile(process.execPath, ['-e', failingCodeWithStdoutErr])
48+
.catch(common.mustCall((err) => {
49+
assert.strictEqual(err.code, 1);
50+
assert.strictEqual(err.stdout, '42\n');
51+
assert.strictEqual(err.stderr, '43\n');
52+
}));
53+
}

0 commit comments

Comments
 (0)