Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c1613c0
internal: add {callback,promis}ify.js
chrisdickinson Feb 1, 2016
a8efd4f
zlib: promisify
chrisdickinson Feb 1, 2016
4af7ec2
repl: promisify
chrisdickinson Feb 1, 2016
0e0a2d9
readline: promisify .question + callbackify completer
chrisdickinson Feb 1, 2016
8798a26
build: add callbackify and promisify to node.gyp
chrisdickinson Feb 1, 2016
355205b
net: promisify socket.setTimeout; add connectAsync
chrisdickinson Feb 1, 2016
7b8e57d
internal: fixup promisify
chrisdickinson Feb 1, 2016
3f2b0d8
http,https: add {request,get}Async
chrisdickinson Feb 1, 2016
23ba073
fs: no callback -> return promise
chrisdickinson Feb 1, 2016
68837b8
dns: promisify
chrisdickinson Feb 1, 2016
3317652
dgram: promisify .{bind,close,send}
chrisdickinson Feb 1, 2016
846bd49
doc,dgram: add promise notes to dgram docs
chrisdickinson Feb 1, 2016
d90b42c
internal: add hooks for specifying different promise resolver
chrisdickinson Feb 1, 2016
adbe352
crypto: promisify pbkdf2, add randomBytesAsync
chrisdickinson Feb 1, 2016
697ce01
child_process,cluster: promisify .send(), add exec{File,}Async
chrisdickinson Feb 1, 2016
f36b62d
add process.setPromiseImplementation
chrisdickinson Feb 1, 2016
082fa08
process: add setPromiseImplementation API
chrisdickinson Feb 1, 2016
54e3001
tls,net: connectAsync should resolve to Socket
chrisdickinson Feb 1, 2016
014fb3e
lib: repromisify
chrisdickinson Feb 2, 2016
15a42c1
repromisify
chrisdickinson Feb 2, 2016
8586b10
src,lib: move back to native promises
chrisdickinson Feb 2, 2016
40ca55e
domain,promise,src: add shim to make promises work with async_wrap + …
chrisdickinson Feb 2, 2016
3928beb
domain: promises capture process.domain at .then time
chrisdickinson Feb 2, 2016
fa725b3
src: back asyncwrap integration out of this pr
chrisdickinson Feb 2, 2016
0528d74
internal: lint
chrisdickinson Feb 3, 2016
010224a
http{,s}: getAsync only listens for error once
chrisdickinson Feb 5, 2016
6ae09c5
http{s,}: nix {request,get}Async
chrisdickinson Feb 10, 2016
670a9c2
rework promisifier
chrisdickinson Feb 10, 2016
240c72d
src: add flag
chrisdickinson Feb 10, 2016
565dfe2
fix handler
chrisdickinson Feb 11, 2016
3384ab0
introduce .promised
chrisdickinson Feb 11, 2016
4e38057
bugfix: use outer arguments
chrisdickinson Feb 11, 2016
8c97549
wip: add recovery object
chrisdickinson Feb 11, 2016
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
Prev Previous commit
Next Next commit
child_process,cluster: promisify .send(), add exec{File,}Async
  • Loading branch information
chrisdickinson committed Feb 1, 2016
commit 697ce018bb1b73872b4f48872c7fec3b4fd71060
59 changes: 47 additions & 12 deletions doc/api/child_process.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ terminated.
*Note: Unlike the `exec()` POSIX system call, `child_process.exec()` does not
replace the existing process and uses a shell to execute the command.*

### child_process.exec(command[, options])

A [`Promise`][def-promise]-returning variant of [`child_process.exec()`][].
Resolves to a two-element array of [Strings][def-string], representing
`stdout` output and `stderr` respectively.

```js
child_process.exec('cat *.js bad_file | wc -l').then((output) => {
console.log(`stdout: ${output[0]}`);
console.log(`stderr: ${output[1]}`);
}).catch((err) => {
console.error(`something went wrong: ${err}`);
});
```

### child_process.execFile(file[, args][, options][, callback])

* `file` {String} A path to an executable file
Expand Down Expand Up @@ -220,6 +235,20 @@ const child = execFile('node', ['--version'], (error, stdout, stderr) => {
});
```

### child_process.execFileAsync(file[, args][, options])

A [`Promise`][def-promise]-returning variant of [`child_process.execFile()`][].
Resolves to a two-element array of [Strings][def-string], representing
`stdout` output and `stderr` respectively.

```js
const execFile = require('child_process').execFile;
const child = execFile('node', ['--version']).then((output) => {
console.log(output[0]); // stdout
console.log(output[1]); // stderr
});
```

### child_process.fork(modulePath[, args][, options])

* `modulePath` {String} The module to run in the child
Expand Down Expand Up @@ -755,7 +784,7 @@ grep.stdin.end();
* `message` {Object}
* `sendHandle` {Handle object}
* `callback` {Function}
* Return: Boolean
* Return: Boolean | 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.

Very uncomfortable with this kind of returns something or Promise kind of pattern. I would rather have a sendAsync kind of variant that always returns a Promise and keep send as it is. Overloading return values can get become problematic for developers who aren't careful.

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.

Just to clarify, one example of how this could become tricky is that the return value on child.send() will be false if the when the backlog of unsent messages exceeds a threshold that makes it unwise to send more, which means a developer can currently depend on the return value being falsy to help with throttling or to provide backpressure. If code is sending a bunch of messages and doesn't care about the callback, then a falsy check would fail...

const child = getChildSomehow();
function sendPing() {
  if (child.connected) {
    if (child.send('ping')) {
      setTimeout(sendPing, 1000);
    } else {
      // wait a bit longer before sending again
      setTimeout(sendPing, 2000);   
    }
  }
}

The fact that callback is already optional on send just makes this unpredictable.

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+, will change. This will likely influence how we approach this with streams as well, since the child process message functionality technically should be a stream.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is it common to node APIs or process.send() is an outlier? I don't remember ever using that return-and-callback-too pattern.
Let me explain my concerns: "return a promise if no callback was provided" seems to be the least annoying way to support async/await. The other option is having a parallel API. Is process.send() the only thing that holds it back?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@gritzko The crypto.randomBytes function is already dual-mode - it will act synchronously without a callback, and asynchronously with a callback. While a different situation from send, I'd imagine it would interfere in a similar way.


When an IPC channel has been established between the parent and child (
i.e. when using [`child_process.fork()`][]), the `child.send()` method can be
Expand All @@ -766,7 +795,7 @@ For example, in the parent script:

```js
const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);
const n = cp.fork(`${\_\_dirname}/sub.js`);

n.on('message', (m) => {
console.log('PARENT got message:', m);
Expand Down Expand Up @@ -801,18 +830,22 @@ passing a TCP server or socket object to the child process. The child will
receive the object as the second argument passed to the callback function
registered on the `process.on('message')` event.

The optional `callback` is a function that is invoked after the message is
sent but before the child may have received it. The function is called with a
single argument: `null` on success, or an [`Error`][] object on failure.
The optional `callback` is a function that is invoked after the message is sent
but before the child may have received it. The function is called with a single
argument: `null` on success, or an [`Error`][] object on failure.

If used with a callback, `child.send()` will return `false` if the channel has
closed or when the backlog of unsent messages exceeds a threshold that makes it
unwise to send more. Otherwise, the method returns `true`. The `callback`
function can be used to implement flow control.

If no `callback` function is provided and the message cannot be sent, an
`'error'` event will be emitted by the `ChildProcess` object. This can happen,
for instance, when the child process has already exited.
When no `callback` is provided, a [`Promise`][def-promise] will be returned.
This promise will resolve after the message has been sent but before the
child has received it.

`child.send()` will return `false` if the channel has closed or when the
backlog of unsent messages exceeds a threshold that makes it unwise to send
more. Otherwise, the method returns `true`. The `callback` function can be
used to implement flow control.
If no `callback` function is provided and the message cannot be sent, the
returned [`Promise`][def-promise] will reject with an Error object. This can
happen, for instance, when the child process has already exited.

#### Example: sending a server object

Expand Down Expand Up @@ -986,4 +1019,6 @@ to the same value.
[`options.detached`]: #child_process_options_detached
[`options.stdio`]: #child_process_options_stdio
[`stdio`]: #child_process_options_stdio
[def-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[def-string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
[synchronous counterparts]: #child_process_synchronous_process_creation
8 changes: 8 additions & 0 deletions lib/child_process.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const util = require('util');
const internalUtil = require('internal/util');
const promisify = require('internal/promisify');
const debug = util.debuglog('child_process');
const constants = require('constants');

Expand Down Expand Up @@ -102,6 +103,9 @@ exports.exec = function(command /*, options, callback*/) {
};


exports.execAsync = promisify(exports.exec);


exports.execFile = function(file /*, args, options, callback*/) {
var args = [], callback;
var options = {
Expand Down Expand Up @@ -288,6 +292,10 @@ exports.execFile = function(file /*, args, options, callback*/) {
return child;
};


exports.execFileAsync = promisify(exports.execFile);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@chrisdickinson something like that came to my mind:

exports.execFileAsync = exports.execFile;
process.promisify === true ? exports.execFileAsync = promisify(exports.execFile) : 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@eljefedelrodeodeljefe Third party libraries wont be able to use this then as process.promisify = true wont be guaranteed. It will also need to be a CLI flag then which adds even more complications.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see. Chris just mentioned a possible opt-out via flag. I'll see where this goes.



var _deprecatedCustomFds = internalUtil.deprecate(function(options) {
options.stdio = options.customFds.map(function(fd) {
return fd === -1 ? 'pipe' : fd;
Expand Down
5 changes: 3 additions & 2 deletions lib/cluster.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const promisify = require('internal/promisify');
const EventEmitter = require('events');
const assert = require('assert');
const dgram = require('dgram');
Expand Down Expand Up @@ -393,7 +394,7 @@ function masterInit() {
cluster.emit('fork', worker);
}

cluster.disconnect = function(cb) {
cluster.disconnect = promisify(function(cb) {
var workers = Object.keys(cluster.workers);
if (workers.length === 0) {
process.nextTick(intercom.emit.bind(intercom, 'disconnect'));
Expand All @@ -405,7 +406,7 @@ function masterInit() {
}
}
if (cb) intercom.once('disconnect', cb);
};
});

Worker.prototype.disconnect = function() {
this.suicide = true;
Expand Down
9 changes: 7 additions & 2 deletions lib/internal/child_process.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const promisify = require('internal/promisify');
const StringDecoder = require('string_decoder').StringDecoder;
const Buffer = require('buffer').Buffer;
const EventEmitter = require('events');
Expand Down Expand Up @@ -498,11 +499,15 @@ function setupChannel(target, channel) {
});
});

target.send = function(message, handle, callback) {
target.send = promisify(function(message, handle, callback) {
if (typeof handle === 'function') {
callback = handle;
handle = undefined;
}
if (typeof message === 'function') {
callback = message;
message = undefined;
}
if (this.connected) {
return this._send(message, handle, false, callback);
}
Expand All @@ -513,7 +518,7 @@ function setupChannel(target, channel) {
this.emit('error', ex); // FIXME(bnoordhuis) Defer to next tick.
}
return false;
};
});

target._send = function(message, handle, swallowErrors, callback) {
assert(this.connected || this._channel);
Expand Down
13 changes: 8 additions & 5 deletions test/parallel/test-child-process-send-returns-boolean.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const emptyFile = path.join(common.fixturesDir, 'empty.js');

const n = fork(emptyFile);

const rv = n.send({ hello: 'world' });
const rv = n.send({ hello: 'world' }, noop);
assert.strictEqual(rv, true);

const spawnOptions = { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] };
Expand All @@ -22,8 +22,11 @@ s.on('exit', function() {

net.createServer(common.fail).listen(common.PORT, function() {
handle = this._handle;
assert.strictEqual(s.send('one', handle), true);
assert.strictEqual(s.send('two', handle), true);
assert.strictEqual(s.send('three'), false);
assert.strictEqual(s.send('four'), false);
assert.strictEqual(s.send('one', handle, noop), true);
assert.strictEqual(s.send('two', handle, noop), true);
assert.strictEqual(s.send('three', noop), false);
assert.strictEqual(s.send('four', noop), false);
});

function noop() {
}