Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
child_process: exec's promisify impl now includes stdout/err 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
  • Loading branch information
giltayar committed Jun 2, 2017
commit e44a057febfd345467e7b9ec8f1b151132a237a3
8 changes: 6 additions & 2 deletions doc/api/child_process.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ child runs longer than `timeout` milliseconds.
replace the existing process and uses a shell to execute the command.

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

For example:

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

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

```js
const util = require('util');
Expand Down
41 changes: 33 additions & 8 deletions lib/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
'use strict';

const util = require('util');
const {
deprecate, convertToValidSignal, customPromisifyArgs
} = require('internal/util');
const { deprecate, convertToValidSignal } = require('internal/util');
const { createPromise,
promiseResolve, promiseReject } = process.binding('util');
const debug = util.debuglog('child_process');

const uv = process.binding('uv');
Expand Down Expand Up @@ -140,9 +140,21 @@ exports.exec = function(command /*, options, callback*/) {
opts.callback);
};

Object.defineProperty(exports.exec, customPromisifyArgs,
{ value: ['stdout', 'stderr'], enumerable: false });

Object.defineProperty(exports.exec, util.promisify.custom, {
enumerable: false, value: function(...args) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we prefer breaking after the ,, and if you want, you can use the value(...args) { shorthand notation

const promise = createPromise();
exports.exec.call(this, ...args, (err, stdout, stderr) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think you need to use .call; exec should work no matter what this refers to

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely, that was my first all. And to use an arrow function instead of a regular function (as the property value). But then I looked at util.promisify and that's what it does, so I got all afraid that in some cases the this in exec may have some meaning.

OK - I'll refactor the value of the property into an arrow function + call exec regulary without .call.

if (err !== null) {
err.stdout = stdout;
err.stderr = stderr;
promiseReject(promise, err);
} else {
promiseResolve(promise, { stdout, stderr });
}
});
return promise;
}
});

exports.execFile = function(file /*, args, options, callback*/) {
var args = [];
Expand Down Expand Up @@ -338,8 +350,21 @@ exports.execFile = function(file /*, args, options, callback*/) {
return child;
};

Object.defineProperty(exports.execFile, customPromisifyArgs,
{ value: ['stdout', 'stderr'], enumerable: false });
Object.defineProperty(exports.execFile, util.promisify.custom, {
enumerable: false, value: function(...args) {
const promise = createPromise();
exports.execFile.call(this, ...args, (err, stdout, stderr) => {
if (err !== null) {
err.stdout = stdout;
err.stderr = stderr;
promiseReject(promise, err);
} else {
promiseResolve(promise, { stdout, stderr });
}
});
return promise;
}
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two functions are nearly identical. It should be okay, but it shouldn’t be too hard to merge these, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do


const _deprecatedCustomFds = deprecate(
function deprecateCustomFds(options) {
Expand Down
19 changes: 19 additions & 0 deletions test/parallel/test-child-process-promisified.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,22 @@ const execFile = promisify(child_process.execFile);
assert(err.message.includes('doesntexist'));
}));
}
const failingCodeWithStdoutErr =
'console.log("out");console.error("err");process.exit(1)';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some people have figured out that you can use template literals for getting good multiline JS code. :)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very much not a fan of multiline termplate literals :-(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer not to - this string goes into the command line, and I have no idea what this will do there, especially not in windows.

{
exec(`${process.execPath} -e '${failingCodeWithStdoutErr}'`)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work on Windows? I think there was some weirdness with single/double quotes. (If you don’t know, that’s okay; CI will tell us.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"A wise man does not fall into a hole that a smart man can get out of" :-). I'll change it to output numbers and then use double quotes like the other tests.

.catch(common.mustCall((err) => {
assert.strictEqual(err.code, 1);
assert.strictEqual(err.stdout, 'out\n');
assert.strictEqual(err.stderr, 'err\n');
}));
}

{
execFile(process.execPath, ['-e', failingCodeWithStdoutErr])
.catch(common.mustCall((err) => {
assert.strictEqual(err.code, 1);
assert.strictEqual(err.stdout, 'out\n');
assert.strictEqual(err.stderr, 'err\n');
}));
}