Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Core support for Promises #5020

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
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
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
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
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
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

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
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
32 changes: 28 additions & 4 deletions doc/api/crypto.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ The `key` is the raw key used by the `algorithm` and `iv` is an
[initialization vector][]. Both arguments must be `'binary'` encoded strings or
[buffers][].

## crypto.createDiffieHellman(prime[, prime_encoding][, generator][, generator_encoding])
### crypto.createDiffieHellman(prime[, prime_encoding][, generator][, generator_encoding])

Creates a `DiffieHellman` key exchange object using the supplied `prime` and an
optional specific `generator`.
Expand Down Expand Up @@ -1074,7 +1074,7 @@ const hashes = crypto.getHashes();
console.log(hashes); // ['sha', 'sha1', 'sha1WithRSAEncryption', ...]
```

### crypto.pbkdf2(password, salt, iterations, keylen, digest, callback)
### crypto.pbkdf2(password, salt, iterations, keylen, digest[, callback])

Provides an asynchronous Password-Based Key Derivation Function 2 (PBKDF2)
implementation. A selected HMAC digest algorithm specified by `digest` is
Expand All @@ -1084,6 +1084,8 @@ applied to derive a key of the requested byte length (`keylen`) from the
The supplied `callback` function is called with two arguments: `err` and
`derivedKey`. If an error occurs, `err` will be set; otherwise `err` will be
null. The successfully generated `derivedKey` will be passed as a [`Buffer`][].
If `callback` is not given, a [`Promise`][def-promise] will be returned that
will resolve to a [`Buffer`][] on success or reject with `err`.

The `iterations` argument must be a number set as high as possible. The
higher the number of iterations, the more secure the derived key will be,
Expand Down Expand Up @@ -1229,7 +1231,8 @@ const crypto = require('crypto');
crypto.randomBytes(256, (err, buf) => {
if (err) throw err;
console.log(
`${buf.length}` bytes of random data: ${buf.toString('hex')});
`${buf.length} bytes of random data: ${buf.toString('hex')}`
);
});
```

Expand All @@ -1241,14 +1244,33 @@ there is a problem generating the bytes.
// Synchronous
const buf = crypto.randomBytes(256);
console.log(
`${buf.length}` bytes of random data: ${buf.toString('hex')});
`${buf.length} bytes of random data: ${buf.toString('hex')}`
);
```

The `crypto.randomBytes()` method will block until there is sufficient entropy.
This should normally never take longer than a few milliseconds. The only time
when generating the random bytes may conceivably block for a longer period of
time is right after boot, when the whole system is still low on entropy.

### crypto.randomBytesAsync(size)

A [`Promise`][def-promise]-returning variant of [`crypto.randomBytes`][].
The return value is always asynchronously generated, resolves to a `Buffer`
on success, or an `Error` object otherwise.

Example:

```js
crypto.randomBytesAsync(256).then((buf) => {
console.log(
`${buf.length} bytes of random data: ${buf.toString('hex')}`
);
}).catch((err) => {
console.error('Something went wrong: ${err.stack}');
});
```

### crypto.setEngine(engine[, flags])

Load and set the `engine` for some or all OpenSSL functions (selected by flags).
Expand Down Expand Up @@ -1333,6 +1355,7 @@ See the reference for other recommendations and details.
[`crypto.createDiffieHellman()`]: #crypto_crypto_creatediffiehellman_prime_prime_encoding_generator_generator_encoding
[`crypto.getHashes()`]: #crypto_crypto_gethashes
[`crypto.pbkdf2`]: #crypto_crypto_pbkdf2_password_salt_iterations_keylen_digest_callback
[`crypto.randomBytes`]: #crypto_crypto_randombytes_size_callback
[`decipher.update`]: #crypto_decipher_update_data_input_encoding_output_encoding
[`diffieHellman.setPublicKey()`]: #crypto_diffiehellman_setpublickey_public_key_encoding
[`EVP_BytesToKey`]: https://www.openssl.org/docs/crypto/EVP_BytesToKey.html
Expand All @@ -1341,6 +1364,7 @@ See the reference for other recommendations and details.
[`Buffer`]: buffer.html
[buffers]: buffer.html
[Caveats]: #crypto_support_for_weak_or_compromised_algorithms
[def-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[initialization vector]: https://en.wikipedia.org/wiki/Initialization_vector
[NIST SP 800-131A]: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf
[NIST SP 800-132]: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf
Expand Down
28 changes: 14 additions & 14 deletions doc/api/dgram.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ underlying socket handle allowing connection handling duties to be shared.
When `exclusive` is `true`, however, the handle is not shared and attempted
port sharing results in an error.

If `callback` is not given, a [`Promise`][def-promise] will be returned, which
will resolve to `undefined` once the socket emits a `'listening'` event.

An example socket listening on an exclusive port is shown below.

```js
Expand All @@ -170,7 +173,9 @@ socket.bind({
### socket.close([callback])

Close the underlying socket and stop listening for data on it. If a callback is
provided, it is added as a listener for the [`'close'`][] event.
provided, it is added as a listener for the [`'close'`][] event. If not provided,
a [`Promise`][def-promise] will be returned, resolving to `undefined` when the
socket is closed.

### socket.dropMembership(multicastAddress[, multicastInterface])

Expand Down Expand Up @@ -215,15 +220,9 @@ If the socket has not been previously bound with a call to `bind`, the socket
is assigned a random port number and is bound to the "all interfaces" address
(`'0.0.0.0'` for `udp4` sockets, `'::0'` for `udp6` sockets.)

An optional `callback` function may be specified to as a way of reporting
DNS errors or for determining when it is safe to reuse the `buf` object.
Note that DNS lookups delay the time to send for at least one tick of the
Node.js event loop.

The only way to know for sure that the datagram has been sent is by using a
`callback`. If an error occurs and a `callback` is given, the error will be
passed as the first argument to the `callback`. If a `callback` is not given,
the error is emitted as an `'error'` event on the `socket` object.
The `callback` parameter is optional. If not provided, `.send()` will return
a [`Promise`][def-promise] resolving to `undefined` that will forward any
errors encountered during `send` as a rejection.

Offset and length are optional, but if you specify one you would need to
specify the other. Also, they are supported only when the first
Expand Down Expand Up @@ -372,9 +371,9 @@ s.bind(1234, () => {

## `dgram` module functions

### dgram.createSocket(options[, callback])
### dgram.createSocket(options[, onmessage])
* `options` Object
* `callback` Function. Attached as a listener to `'message'` events.
* `onmessage` Function. Attached as a listener to `'message'` events.
* Returns: Socket object

Creates a `dgram.Socket` object. The `options` argument is an object that
Expand All @@ -393,10 +392,10 @@ interfaces" address on a random port (it does the right thing for both `udp4`
and `udp6` sockets). The bound address and port can be retrieved using
[`socket.address().address`][] and [`socket.address().port`][].

## dgram.createSocket(type[, callback])
### dgram.createSocket(type[, onmessage])

* `type` String. Either 'udp4' or 'udp6'
* `callback` Function. Attached as a listener to `'message'` events.
* `onmessage` Function. Attached as a listener to `'message'` events.
Optional
* Returns: Socket object

Expand All @@ -423,3 +422,4 @@ and `udp6` sockets). The bound address and port can be retrieved using
[`socket.address().port`]: #dgram_socket_address
[`socket.bind()`]: #dgram_socket_bind_port_address_callback
[byte length]: buffer.html#buffer_class_method_buffer_bytelength_string_encoding
[def-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
45 changes: 45 additions & 0 deletions doc/api/http.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,20 @@ http.get('http://www.google.com/index.html', (res) => {
});
```

## http.getAsync(options)

A [`Promise`][def-promise]-returning version of [`http.get()`][]. Returns an
object containing a `Promise` resolving to a [`http.IncomingMessage`][] Parses
`options` the same as [`http.get()`][]. Request errors will reject the promise.

Example:

```js
http.getAsync('http://www.google.com/index.html').then((res) => {
res.pipe(process.stdout);
});
```

## http.globalAgent

Global instance of Agent which is used as the default for all http client
Expand Down Expand Up @@ -1157,6 +1171,37 @@ There are a few special headers that should be noted.
* Sending an Authorization header will override using the `auth` option
to compute basic authentication.

## http.requestAsync(options)

* Returns: {Object} with attributes:
* `request` - a [`http.ClientRequest`][].
* `response` - a [`Promise`][def-promise] for a [`http.IncomingMessage`][].

A [`Promise`][def-promise]-returning version of [`http.request()`][]. Returns
an object containing a [`http.ClientRequest`][] and a `Promise` resolving to
a [`http.IncomingMessage`][] as `request` and `response`, respectively. Parses
`options` the same as [`http.request()`][].

Example:

```js
const http = require('http');
const url = require('url');

const pair = http.requestAsync(url.parse('http://localhost:8080/'))

pair.request.end();
pair.response.then((resp) => {
const accumulator = [];
resp.on('data', (chunk) => {
accumulator.push(chunk);
});
resp.on('end', () => {
console.log(Buffer.concat(accumulator).toString('utf8'));
});
});
```

[`'checkContinue'`]: #http_event_checkcontinue
[`'listening'`]: net.html#net_event_listening
[`'response'`]: #http_event_response
Expand Down
35 changes: 35 additions & 0 deletions doc/api/https.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,30 @@ https.get('https://encrypted.google.com/', (res) => {
});
```

## https.getAsync(options)

Like [`http.getAsync()`][] but for HTTPS. If

`options` can be an object or a string. If `options` is a string, it is
automatically parsed with [`url.parse()`][].

Example:

```js
const https = require('https');

https.getAsync('https://encrypted.google.com/').then((res) => {
console.log('statusCode: ', res.statusCode);
console.log('headers: ', res.headers);

res.on('data', (d) => {
process.stdout.write(d);
});
}).catch((err) => {
console.error(err);
});
```

## https.globalAgent

Global instance of [`https.Agent`][] for all HTTPS client requests.
Expand Down Expand Up @@ -227,6 +251,17 @@ var req = https.request(options, (res) => {
}
```

## https.requestAsync(options)

* Returns: {Object} with attributes:
* `request` - a [`http.ClientRequest`][].
* `response` - a [`Promise`][def-promise] for a [`http.IncomingMessage`][].

A [`Promise`][def-promise]-returning version of [`https.request()`][]. Returns
an object containing a [`http.ClientRequest`][] and a `Promise` resolving to
a [`http.IncomingMessage`][] as `request` and `response`, respectively. Parses
`options` the same as [`http.request()`][].

[`Agent`]: #https_class_https_agent
[`globalAgent`]: #https_https_globalagent
[`http.Agent`]: http.html#http_class_http_agent
Expand Down
10 changes: 9 additions & 1 deletion doc/api/net.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,9 @@ If `timeout` is 0, then the existing idle timeout is disabled.
The optional `callback` parameter will be added as a one time listener for the
[`'timeout'`][] event.

Returns `socket`.
Returns `socket` if `callback` is provided, otherwise returns a
[`Promise`][def-promise] that resolves to `undefined` once the timeout has
fired.

### socket.unref()

Expand Down Expand Up @@ -568,6 +570,11 @@ If `host` is omitted, `'localhost'` will be assumed.
The `connectListener` parameter will be added as a listener for the
[`'connect'`][] event once.

## net.connectAsync(options)

Returns a [`Promise`][def-promise] for a new [`net.Socket`][] that resolves
when the socket has connected.

## net.createConnection(options[, connectListener])

A factory function, which returns a new [`net.Socket`][] and automatically
Expand Down Expand Up @@ -728,4 +735,5 @@ Returns true if input is a version 6 IP address, otherwise returns false.
[`socket.connect`]: #net_socket_connect_options_connectlistener
[`socket.setTimeout()`]: #net_socket_settimeout_timeout_callback
[`stream.setEncoding()`]: stream.html#stream_readable_setencoding_encoding
[def-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[Readable Stream]: stream.html#stream_class_stream_readable
Loading