From c1613c01a0808b246e333fb846bb0d297e8622db Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 17:18:11 -0800 Subject: [PATCH 01/33] internal: add {callback,promis}ify.js Callbackify turns synchronous and promise-returning functions into callback-accepting functions. Promisify turns callback-accepting functions into promise-returning functions iff the last argument is not a function. --- lib/internal/callbackify.js | 34 ++++++++++++++++++++++++++++++++ lib/internal/promisify.js | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 lib/internal/callbackify.js create mode 100644 lib/internal/promisify.js diff --git a/lib/internal/callbackify.js b/lib/internal/callbackify.js new file mode 100644 index 00000000000000..af07dc86f041ec --- /dev/null +++ b/lib/internal/callbackify.js @@ -0,0 +1,34 @@ +'use strict'; + +module.exports = callbackify; + +function callbackify (fn) { + Object.defineProperty(inner, 'name', { + writable: false, + enumerable: false, + configurable: true, + value: fn.name + 'Callback' + }); + return inner; + + function inner () { + var resolve = null; + var reject = null; + const cb = arguments[arguments.length - 1]; + if (typeof cb !== 'function') { + cb = err => { throw err; }; + } + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + try { + Promise.resolve(fn.apply(this, arguments)).then( + result => cb(null, result), + error => cb(error) + ); + } catch (err) { + cb(err); + } + } +} diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js new file mode 100644 index 00000000000000..9c39fab5b923f0 --- /dev/null +++ b/lib/internal/promisify.js @@ -0,0 +1,39 @@ +'use strict'; + +module.exports = promisify; + +function promisify (fn) { + Object.defineProperty(inner, 'name', { + writable: false, + enumerable: false, + configurable: true, + value: fn.name + 'Promisified' + }); + return inner; + + function inner () { + if (typeof arguments[arguments.length - 1] === 'function') { + return fn.apply(this, arguments); + } + var resolve = null; + var reject = null; + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + try { + fn.apply(this, [...arguments, function (err) { + if (err) { + return reject(err); + } + return arguments.length === 2 + ? resolve(arguments[1]) + : resolve(resolver(...[...arguments].slice(1))); + }]); + } catch (err) { + reject(err); + } + return promise; + } +} + From a8efd4fcc875d7548bdf84d281b0975dc3ea5173 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 17:19:54 -0800 Subject: [PATCH 02/33] zlib: promisify --- lib/zlib.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/zlib.js b/lib/zlib.js index 3e6f7b187632f2..3bb1b124767c8c 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -8,6 +8,7 @@ const assert = require('assert').ok; const kMaxLength = require('buffer').kMaxLength; const kRangeErrorMessage = 'Cannot create final Buffer. ' + 'It would be larger than 0x' + kMaxLength.toString(16) + ' bytes'; +const promisify = require('internal/promisify'); // zlib doesn't provide these, so kludge them in following the same // const naming scheme zlib uses. @@ -103,85 +104,85 @@ exports.createUnzip = function(o) { // Convenience methods. // compress/decompress a string or buffer in one step. -exports.deflate = function(buffer, opts, callback) { +exports.deflate = promisify(function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Deflate(opts), buffer, callback); -}; +}); exports.deflateSync = function(buffer, opts) { return zlibBufferSync(new Deflate(opts), buffer); }; -exports.gzip = function(buffer, opts, callback) { +exports.gzip = promisify(function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Gzip(opts), buffer, callback); -}; +}); exports.gzipSync = function(buffer, opts) { return zlibBufferSync(new Gzip(opts), buffer); }; -exports.deflateRaw = function(buffer, opts, callback) { +exports.deflateRaw = promisify(function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new DeflateRaw(opts), buffer, callback); -}; +}); exports.deflateRawSync = function(buffer, opts) { return zlibBufferSync(new DeflateRaw(opts), buffer); }; -exports.unzip = function(buffer, opts, callback) { +exports.unzip = promisify(function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Unzip(opts), buffer, callback); -}; +}); exports.unzipSync = function(buffer, opts) { return zlibBufferSync(new Unzip(opts), buffer); }; -exports.inflate = function(buffer, opts, callback) { +exports.inflate = promisify(function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Inflate(opts), buffer, callback); -}; +}); exports.inflateSync = function(buffer, opts) { return zlibBufferSync(new Inflate(opts), buffer); }; -exports.gunzip = function(buffer, opts, callback) { +exports.gunzip = promisify(function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Gunzip(opts), buffer, callback); -}; +}); exports.gunzipSync = function(buffer, opts) { return zlibBufferSync(new Gunzip(opts), buffer); }; -exports.inflateRaw = function(buffer, opts, callback) { +exports.inflateRaw = promisify(function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new InflateRaw(opts), buffer, callback); -}; +}); exports.inflateRawSync = function(buffer, opts) { return zlibBufferSync(new InflateRaw(opts), buffer); From 4af7ec24e92cc667faf45db0b7574919eb52330d Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 17:20:07 -0800 Subject: [PATCH 03/33] repl: promisify --- lib/repl.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index f80555dba41afc..48fa6f5862eddc 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -22,6 +22,7 @@ 'use strict'; const internalModule = require('internal/module'); +const promisify = require('internal/promisify'); const internalUtil = require('internal/util'); const util = require('util'); const inherits = util.inherits; @@ -626,7 +627,7 @@ function filteredOwnPropertyNames(obj) { // // Warning: This eval's code like "foo.bar.baz", so it will run property // getter code. -REPLServer.prototype.complete = function(line, callback) { +REPLServer.prototype.complete = promisify(function(line, callback) { // There may be local variables to evaluate, try a nested REPL if (this.bufferedCommand !== undefined && this.bufferedCommand.length) { // Get a new array of inputed lines @@ -884,7 +885,7 @@ REPLServer.prototype.complete = function(line, callback) { callback(null, [completions || [], completeOn]); } -}; +}); /** From 0e0a2d9554299c1ee96a866d86b3bfe8b55f1c3a Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 17:20:32 -0800 Subject: [PATCH 04/33] readline: promisify .question + callbackify completer --- lib/readline.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index 065cc62807c689..ee81ffb36387e1 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -11,6 +11,8 @@ const kHistorySize = 30; const util = require('util'); const debug = util.debuglog('readline'); const internalUtil = require('internal/util'); +const callbackify = require('internal/callbackify'); +const promisify = require('internal/promisify'); const inherits = util.inherits; const Buffer = require('buffer').Buffer; const EventEmitter = require('events'); @@ -79,9 +81,9 @@ function Interface(input, output, completer, terminal) { // Check arity, 2 - for async, 1 for sync if (typeof completer === 'function') { - this.completer = completer.length === 2 ? completer : function(v, cb) { - cb(null, completer(v)); - }; + this.completer = completer.length === 2 + ? completer + : callbackify(completer); } this.setPrompt('> '); @@ -192,7 +194,7 @@ Interface.prototype.prompt = function(preserveCursor) { }; -Interface.prototype.question = function(query, cb) { +Interface.prototype.question = promisify(function(query, cb) { if (typeof cb === 'function') { if (this._questionCallback) { this.prompt(); @@ -203,7 +205,7 @@ Interface.prototype.question = function(query, cb) { this.prompt(); } } -}; +}); Interface.prototype._onLine = function(line) { From 8798a26f9843bd5fd57b6a3434789c9af1846e4c Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 17:20:53 -0800 Subject: [PATCH 05/33] build: add callbackify and promisify to node.gyp --- node.gyp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/node.gyp b/node.gyp index a65f76e4ce68db..0bb533fb9b3f3a 100644 --- a/node.gyp +++ b/node.gyp @@ -69,10 +69,12 @@ 'lib/v8.js', 'lib/vm.js', 'lib/zlib.js', + 'lib/internal/callbackify.js', 'lib/internal/child_process.js', 'lib/internal/cluster.js', 'lib/internal/freelist.js', 'lib/internal/linkedlist.js', + 'lib/internal/promisify.js', 'lib/internal/net.js', 'lib/internal/module.js', 'lib/internal/readline.js', From 355205b36de46c1bdfa9ababe98ce497c4607b31 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 18:54:18 -0800 Subject: [PATCH 06/33] net: promisify socket.setTimeout; add connectAsync --- doc/api/net.markdown | 10 +++++++++- lib/net.js | 16 ++++++++++++++-- test/parallel/test-http-set-timeout.js | 15 +++++++++++++-- test/parallel/test-net-socket-timeout.js | 2 +- test/parallel/test-tls-request-timeout.js | 12 ++++++++---- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/doc/api/net.markdown b/doc/api/net.markdown index 1cd14b563fc439..f1bdf8de0f9820 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -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() @@ -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 @@ -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 diff --git a/lib/net.js b/lib/net.js index fca70fb51fa054..08da6052e7ec6d 100644 --- a/lib/net.js +++ b/lib/net.js @@ -5,6 +5,7 @@ const stream = require('stream'); const timers = require('timers'); const util = require('util'); const internalUtil = require('internal/util'); +const promisify = require('internal/promisify'); const internalNet = require('internal/net'); const assert = require('assert'); const cares = process.binding('cares_wrap'); @@ -65,6 +66,13 @@ exports.connect = exports.createConnection = function() { return Socket.prototype.connect.apply(s, args); }; +exports.connectAsync = promisify(function() { + var args = normalizeConnectArgs(arguments); + debug('createConnection', args); + var s = new Socket(args[0]); + return Socket.prototype.connect.apply(s, args); +}); + // Returns an array [options] or [options, cb] // It is the same as the argument of Socket.prototype.connect(). function normalizeConnectArgs(args) { @@ -298,7 +306,11 @@ Socket.prototype.listen = function() { }; -Socket.prototype.setTimeout = function(msecs, callback) { +Socket.prototype.setTimeout = promisify(function(msecs, callback) { + if (typeof msecs === 'function') { + callback = msecs; + msecs = 0; + } if (msecs === 0) { timers.unenroll(this); if (callback) { @@ -312,7 +324,7 @@ Socket.prototype.setTimeout = function(msecs, callback) { } } return this; -}; +}); Socket.prototype._onTimeout = function() { diff --git a/test/parallel/test-http-set-timeout.js b/test/parallel/test-http-set-timeout.js index 9bda60b2226443..c1a9cae1e81e12 100644 --- a/test/parallel/test-http-set-timeout.js +++ b/test/parallel/test-http-set-timeout.js @@ -7,12 +7,23 @@ var net = require('net'); var server = http.createServer(function(req, res) { console.log('got request. setting 1 second timeout'); var s = req.connection.setTimeout(500); - assert.ok(s instanceof net.Socket); + var pending = 2; + s.then(() => { + if (!--pending) { + closeServer(); + } + }); req.connection.on('timeout', function() { + if (!--pending) { + closeServer(); + } + }); + + function closeServer () { req.connection.destroy(); console.error('TIMEOUT'); server.close(); - }); + } }); server.listen(common.PORT, function() { diff --git a/test/parallel/test-net-socket-timeout.js b/test/parallel/test-net-socket-timeout.js index 5ab11909b4c306..352a7a189060e6 100644 --- a/test/parallel/test-net-socket-timeout.js +++ b/test/parallel/test-net-socket-timeout.js @@ -6,7 +6,7 @@ var assert = require('assert'); // Verify that invalid delays throw var noop = function() {}; var s = new net.Socket(); -var nonNumericDelays = ['100', true, false, undefined, null, '', {}, noop, []]; +var nonNumericDelays = ['100', true, false, undefined, null, '', {}, []]; var badRangeDelays = [-0.001, -1, -Infinity, Infinity, NaN]; var validDelays = [0, 0.001, 1, 1e6]; diff --git a/test/parallel/test-tls-request-timeout.js b/test/parallel/test-tls-request-timeout.js index 0db2a613afc9e4..405c5c3cc8c355 100644 --- a/test/parallel/test-tls-request-timeout.js +++ b/test/parallel/test-tls-request-timeout.js @@ -10,7 +10,7 @@ var tls = require('tls'); var fs = require('fs'); -var hadTimeout = false; +var hadTimeout = 0; var options = { key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), @@ -19,10 +19,14 @@ var options = { var server = tls.Server(options, function(socket) { var s = socket.setTimeout(100); - assert.ok(s instanceof tls.TLSSocket); + var pending = 2; + + s.then(() => { + ++hadTimeout; + }); socket.on('timeout', function(err) { - hadTimeout = true; + ++hadTimeout; socket.end(); server.close(); }); @@ -36,5 +40,5 @@ server.listen(common.PORT, function() { }); process.on('exit', function() { - assert.ok(hadTimeout); + assert.equal(hadTimeout, 2); }); From 7b8e57dd07d8f468c3cac6d75aaa84d72e90a2c8 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 18:54:41 -0800 Subject: [PATCH 07/33] internal: fixup promisify --- lib/internal/promisify.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index 9c39fab5b923f0..7805f40cfbf8a3 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -26,9 +26,12 @@ function promisify (fn) { if (err) { return reject(err); } - return arguments.length === 2 - ? resolve(arguments[1]) - : resolve(resolver(...[...arguments].slice(1))); + switch (arguments.length) { + case 0: return resolve(); + case 1: return resolve(); + case 2: return resolve(arguments[1]); + } + return resolve(resolver(...[...arguments].slice(1))); }]); } catch (err) { reject(err); @@ -36,4 +39,3 @@ function promisify (fn) { return promise; } } - From 3f2b0d8a12a9ab3f58b13442c7610ca099725a17 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 19:23:33 -0800 Subject: [PATCH 08/33] http,https: add {request,get}Async --- doc/api/http.markdown | 45 +++++++++++++++++++++++ doc/api/https.markdown | 35 ++++++++++++++++++ lib/http.js | 34 +++++++++++++++++ lib/https.js | 36 +++++++++++++++++- test/parallel/test-http-set-timeout.js | 4 +- test/parallel/test-tls-request-timeout.js | 1 - 6 files changed, 150 insertions(+), 5 deletions(-) diff --git a/doc/api/http.markdown b/doc/api/http.markdown index fc55777acf6ada..d3d45fb3ef696a 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -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 @@ -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 diff --git a/doc/api/https.markdown b/doc/api/https.markdown index 39401de8c5135c..a19fd61baf565c 100644 --- a/doc/api/https.markdown +++ b/doc/api/https.markdown @@ -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. @@ -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 diff --git a/lib/http.js b/lib/http.js index 64788dd5c07f9f..cab8c0dbfb0fdc 100644 --- a/lib/http.js +++ b/lib/http.js @@ -31,12 +31,46 @@ exports.request = function(options, cb) { return new ClientRequest(options, cb); }; +exports.requestAsync = function(options) { + var resolveRequest = null; + const getResponse = new Promise((resolve) => { + resolveRequest = resolve; + }); + const request = exports.request(options, (res) => { + resolveRequest(res); + }); + return { + request, + response: getResponse + }; +}; + exports.get = function(options, cb) { var req = exports.request(options, cb); req.end(); return req; }; +exports.getAsync = function(options) { + var resolveRequest = null; + var rejectRequest = null; + const getResponse = new Promise((resolve, reject) => { + resolveRequest = resolve; + rejectRequest = reject; + }); + const request = exports.request(options, (res) => { + resolveRequest(res); + }); + request.on('error', rejectRequest); + getResponse.then(() => { + request.removeListener('error', rejectRequest); + }); + return { + request, + response: getResponse + }; +}; + exports._connectionListener = server._connectionListener; const Server = exports.Server = server.Server; diff --git a/lib/https.js b/lib/https.js index 7008a79131c663..a21b48b1ad358b 100644 --- a/lib/https.js +++ b/lib/https.js @@ -182,8 +182,42 @@ exports.request = function(options, cb) { return http.request(options, cb); }; +exports.requestAsync = function(options) { + var resolveRequest = null; + const getResponse = new Promise((resolve) => { + resolveRequest = resolve; + }); + const request = exports.request(options, (res) => { + resolveRequest(res); + }); + return { + request, + response: getResponse + }; +}; + exports.get = function(options, cb) { - var req = exports.request(options, cb); + const req = exports.request(options, cb); req.end(); return req; }; + +exports.getAsync = function(options) { + var resolveRequest = null; + var rejectRequest = null; + const getResponse = new Promise((resolve, reject) => { + resolveRequest = resolve; + rejectRequest = reject; + }); + const request = exports.request(options, (res) => { + resolveRequest(res); + }); + request.on('error', rejectRequest); + getResponse.then(() => { + request.removeListener('error', rejectRequest); + }); + return { + request, + response: getResponse + }; +}; diff --git a/test/parallel/test-http-set-timeout.js b/test/parallel/test-http-set-timeout.js index c1a9cae1e81e12..8d77bae74b3feb 100644 --- a/test/parallel/test-http-set-timeout.js +++ b/test/parallel/test-http-set-timeout.js @@ -1,8 +1,6 @@ 'use strict'; var common = require('../common'); -var assert = require('assert'); var http = require('http'); -var net = require('net'); var server = http.createServer(function(req, res) { console.log('got request. setting 1 second timeout'); @@ -19,7 +17,7 @@ var server = http.createServer(function(req, res) { } }); - function closeServer () { + function closeServer() { req.connection.destroy(); console.error('TIMEOUT'); server.close(); diff --git a/test/parallel/test-tls-request-timeout.js b/test/parallel/test-tls-request-timeout.js index 405c5c3cc8c355..24a7ef6e9c0c87 100644 --- a/test/parallel/test-tls-request-timeout.js +++ b/test/parallel/test-tls-request-timeout.js @@ -19,7 +19,6 @@ var options = { var server = tls.Server(options, function(socket) { var s = socket.setTimeout(100); - var pending = 2; s.then(() => { ++hadTimeout; From 23ba07370d19e40b0548088e3d9b096c1227aebd Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 20:22:59 -0800 Subject: [PATCH 09/33] fs: no callback -> return promise --- lib/fs.js | 121 ++++++++++++------------ lib/internal/promisify.js | 29 +++--- test/fixtures/test-fs-readfile-error.js | 1 - test/parallel/test-fs-access.js | 8 -- test/parallel/test-fs-readfile-error.js | 42 -------- 5 files changed, 74 insertions(+), 127 deletions(-) delete mode 100644 test/fixtures/test-fs-readfile-error.js delete mode 100644 test/parallel/test-fs-readfile-error.js diff --git a/lib/fs.js b/lib/fs.js index af8001bb181698..a58e3e977fbe9e 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -5,6 +5,7 @@ const SlowBuffer = require('buffer').SlowBuffer; const util = require('util'); +const promisify = require('internal/promisify'); const pathModule = require('path'); const binding = process.binding('fs'); @@ -179,7 +180,7 @@ fs.Stats.prototype.isSocket = function() { }); }); -fs.access = function(path, mode, callback) { +fs.access = promisify(function(path, mode, callback) { if (typeof mode === 'function') { callback = mode; mode = fs.F_OK; @@ -194,7 +195,7 @@ fs.access = function(path, mode, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.access(pathModule._makeLong(path), mode, req); -}; +}); fs.accessSync = function(path, mode) { nullCheck(path); @@ -207,7 +208,7 @@ fs.accessSync = function(path, mode) { binding.access(pathModule._makeLong(path), mode); }; -fs.exists = function(path, callback) { +fs.exists = promisify(function(path, callback) { if (!nullCheck(path, cb)) return; var req = new FSReqWrap(); req.oncomplete = cb; @@ -215,7 +216,7 @@ fs.exists = function(path, callback) { function cb(err, stats) { if (callback) callback(err ? false : true); } -}; +}, 1); fs.existsSync = function(path) { try { @@ -227,7 +228,7 @@ fs.existsSync = function(path) { } }; -fs.readFile = function(path, options, callback_) { +fs.readFile = promisify(function(path, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); if (!options || typeof options === 'function') { @@ -263,7 +264,7 @@ fs.readFile = function(path, options, callback_) { stringToFlags(flag), 0o666, req); -}; +}); const kReadFileBufferLength = 8 * 1024; @@ -543,11 +544,11 @@ Object.defineProperty(exports, '_stringToFlags', { // Yes, the follow could be easily DRYed up but I provide the explicit // list to make the arguments clear. -fs.close = function(fd, callback) { +fs.close = promisify(function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.close(fd, req); -}; +}); fs.closeSync = function(fd) { return binding.close(fd); @@ -563,7 +564,7 @@ function modeNum(m, def) { return undefined; } -fs.open = function(path, flags, mode, callback_) { +fs.open = promisify(function(path, flags, mode, callback_) { var callback = makeCallback(arguments[arguments.length - 1]); mode = modeNum(mode, 0o666); @@ -576,7 +577,7 @@ fs.open = function(path, flags, mode, callback_) { stringToFlags(flags), mode, req); -}; +}); fs.openSync = function(path, flags, mode) { mode = modeNum(mode, 0o666); @@ -726,7 +727,7 @@ fs.writeSync = function(fd, buffer, offset, length, position) { return binding.writeString(fd, buffer, offset, length, position); }; -fs.rename = function(oldPath, newPath, callback) { +fs.rename = promisify(function(oldPath, newPath, callback) { callback = makeCallback(callback); if (!nullCheck(oldPath, callback)) return; if (!nullCheck(newPath, callback)) return; @@ -735,7 +736,7 @@ fs.rename = function(oldPath, newPath, callback) { binding.rename(pathModule._makeLong(oldPath), pathModule._makeLong(newPath), req); -}; +}); fs.renameSync = function(oldPath, newPath) { nullCheck(oldPath); @@ -788,7 +789,7 @@ fs.truncateSync = function(path, len) { return ret; }; -fs.ftruncate = function(fd, len, callback) { +fs.ftruncate = promisify(function(fd, len, callback) { if (typeof len === 'function') { callback = len; len = 0; @@ -798,7 +799,7 @@ fs.ftruncate = function(fd, len, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.ftruncate(fd, len, req); -}; +}); fs.ftruncateSync = function(fd, len) { if (len === undefined) { @@ -807,40 +808,40 @@ fs.ftruncateSync = function(fd, len) { return binding.ftruncate(fd, len); }; -fs.rmdir = function(path, callback) { +fs.rmdir = promisify(function(path, callback) { callback = maybeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.rmdir(pathModule._makeLong(path), req); -}; +}); fs.rmdirSync = function(path) { nullCheck(path); return binding.rmdir(pathModule._makeLong(path)); }; -fs.fdatasync = function(fd, callback) { +fs.fdatasync = promisify(function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fdatasync(fd, req); -}; +}); fs.fdatasyncSync = function(fd) { return binding.fdatasync(fd); }; -fs.fsync = function(fd, callback) { +fs.fsync = promisify(function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fsync(fd, req); -}; +}); fs.fsyncSync = function(fd) { return binding.fsync(fd); }; -fs.mkdir = function(path, mode, callback) { +fs.mkdir = promisify(function(path, mode, callback) { if (typeof mode === 'function') callback = mode; callback = makeCallback(callback); if (!nullCheck(path, callback)) return; @@ -849,7 +850,7 @@ fs.mkdir = function(path, mode, callback) { binding.mkdir(pathModule._makeLong(path), modeNum(mode, 0o777), req); -}; +}); fs.mkdirSync = function(path, mode) { nullCheck(path); @@ -857,40 +858,40 @@ fs.mkdirSync = function(path, mode) { modeNum(mode, 0o777)); }; -fs.readdir = function(path, callback) { +fs.readdir = promisify(function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.readdir(pathModule._makeLong(path), req); -}; +}); fs.readdirSync = function(path) { nullCheck(path); return binding.readdir(pathModule._makeLong(path)); }; -fs.fstat = function(fd, callback) { +fs.fstat = promisify(function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fstat(fd, req); -}; +}); -fs.lstat = function(path, callback) { +fs.lstat = promisify(function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.lstat(pathModule._makeLong(path), req); -}; +}); -fs.stat = function(path, callback) { +fs.stat = promisify(function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.stat(pathModule._makeLong(path), req); -}; +}); fs.fstatSync = function(fd) { return binding.fstat(fd); @@ -906,13 +907,13 @@ fs.statSync = function(path) { return binding.stat(pathModule._makeLong(path)); }; -fs.readlink = function(path, callback) { +fs.readlink = promisify(function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.readlink(pathModule._makeLong(path), req); -}; +}); fs.readlinkSync = function(path) { nullCheck(path); @@ -934,7 +935,7 @@ function preprocessSymlinkDestination(path, type, linkPath) { } } -fs.symlink = function(target, path, type_, callback_) { +fs.symlink = promisify(function(target, path, type_, callback_) { var type = (typeof type_ === 'string' ? type_ : null); var callback = makeCallback(arguments[arguments.length - 1]); @@ -948,7 +949,7 @@ fs.symlink = function(target, path, type_, callback_) { pathModule._makeLong(path), type, req); -}; +}); fs.symlinkSync = function(target, path, type) { type = (typeof type === 'string' ? type : null); @@ -961,7 +962,7 @@ fs.symlinkSync = function(target, path, type) { type); }; -fs.link = function(srcpath, dstpath, callback) { +fs.link = promisify(function(srcpath, dstpath, callback) { callback = makeCallback(callback); if (!nullCheck(srcpath, callback)) return; if (!nullCheck(dstpath, callback)) return; @@ -972,7 +973,7 @@ fs.link = function(srcpath, dstpath, callback) { binding.link(pathModule._makeLong(srcpath), pathModule._makeLong(dstpath), req); -}; +}); fs.linkSync = function(srcpath, dstpath) { nullCheck(srcpath); @@ -981,31 +982,31 @@ fs.linkSync = function(srcpath, dstpath) { pathModule._makeLong(dstpath)); }; -fs.unlink = function(path, callback) { +fs.unlink = promisify(function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.unlink(pathModule._makeLong(path), req); -}; +}); fs.unlinkSync = function(path) { nullCheck(path); return binding.unlink(pathModule._makeLong(path)); }; -fs.fchmod = function(fd, mode, callback) { +fs.fchmod = promisify(function(fd, mode, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fchmod(fd, modeNum(mode), req); -}; +}); fs.fchmodSync = function(fd, mode) { return binding.fchmod(fd, modeNum(mode)); }; if (constants.hasOwnProperty('O_SYMLINK')) { - fs.lchmod = function(path, mode, callback) { + fs.lchmod = promisify(function(path, mode, callback) { callback = maybeCallback(callback); fs.open(path, constants.O_WRONLY | constants.O_SYMLINK, function(err, fd) { if (err) { @@ -1020,7 +1021,7 @@ if (constants.hasOwnProperty('O_SYMLINK')) { }); }); }); - }; + }); fs.lchmodSync = function(path, mode) { var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); @@ -1044,7 +1045,7 @@ if (constants.hasOwnProperty('O_SYMLINK')) { } -fs.chmod = function(path, mode, callback) { +fs.chmod = promisify(function(path, mode, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); @@ -1052,7 +1053,7 @@ fs.chmod = function(path, mode, callback) { binding.chmod(pathModule._makeLong(path), modeNum(mode), req); -}; +}); fs.chmodSync = function(path, mode) { nullCheck(path); @@ -1060,7 +1061,7 @@ fs.chmodSync = function(path, mode) { }; if (constants.hasOwnProperty('O_SYMLINK')) { - fs.lchown = function(path, uid, gid, callback) { + fs.lchown = promisify(function(path, uid, gid, callback) { callback = maybeCallback(callback); fs.open(path, constants.O_WRONLY | constants.O_SYMLINK, function(err, fd) { if (err) { @@ -1069,7 +1070,7 @@ if (constants.hasOwnProperty('O_SYMLINK')) { } fs.fchown(fd, uid, gid, callback); }); - }; + }); fs.lchownSync = function(path, uid, gid) { var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); @@ -1077,23 +1078,23 @@ if (constants.hasOwnProperty('O_SYMLINK')) { }; } -fs.fchown = function(fd, uid, gid, callback) { +fs.fchown = promisify(function(fd, uid, gid, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fchown(fd, uid, gid, req); -}; +}); fs.fchownSync = function(fd, uid, gid) { return binding.fchown(fd, uid, gid); }; -fs.chown = function(path, uid, gid, callback) { +fs.chown = promisify(function(path, uid, gid, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.chown(pathModule._makeLong(path), uid, gid, req); -}; +}); fs.chownSync = function(path, uid, gid) { nullCheck(path); @@ -1121,7 +1122,7 @@ function toUnixTimestamp(time) { // exported for unit tests, not for public consumption fs._toUnixTimestamp = toUnixTimestamp; -fs.utimes = function(path, atime, mtime, callback) { +fs.utimes = promisify(function(path, atime, mtime, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); @@ -1130,7 +1131,7 @@ fs.utimes = function(path, atime, mtime, callback) { toUnixTimestamp(atime), toUnixTimestamp(mtime), req); -}; +}); fs.utimesSync = function(path, atime, mtime) { nullCheck(path); @@ -1139,13 +1140,13 @@ fs.utimesSync = function(path, atime, mtime) { binding.utimes(pathModule._makeLong(path), atime, mtime); }; -fs.futimes = function(fd, atime, mtime, callback) { +fs.futimes = promisify(function(fd, atime, mtime, callback) { atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.futimes(fd, atime, mtime, req); -}; +}); fs.futimesSync = function(fd, atime, mtime) { atime = toUnixTimestamp(atime); @@ -1185,7 +1186,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, position, callback_) { }); } -fs.writeFile = function(path, data, options, callback_) { +fs.writeFile = promisify(function(path, data, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); if (!options || typeof options === 'function') { @@ -1220,7 +1221,7 @@ fs.writeFile = function(path, data, options, callback_) { writeAll(fd, isUserFd, buffer, 0, buffer.length, position, callback); } -}; +}); fs.writeFileSync = function(path, data, options) { if (!options) { @@ -1257,7 +1258,7 @@ fs.writeFileSync = function(path, data, options) { } }; -fs.appendFile = function(path, data, options, callback_) { +fs.appendFile = promisify(function(path, data, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); if (!options || typeof options === 'function') { @@ -1276,7 +1277,7 @@ fs.appendFile = function(path, data, options, callback_) { options.flag = 'a'; fs.writeFile(path, data, options, callback); -}; +}); fs.appendFileSync = function(path, data, options) { if (!options) { @@ -1570,7 +1571,7 @@ fs.realpathSync = function realpathSync(p, cache) { }; -fs.realpath = function realpath(p, cache, cb) { +fs.realpath = promisify(function realpath(p, cache, cb) { if (typeof cb !== 'function') { cb = maybeCallback(cache); cache = null; @@ -1690,7 +1691,7 @@ fs.realpath = function realpath(p, cache, cb) { p = pathModule.resolve(resolvedLink, p.slice(pos)); start(); } -}; +}); var pool; diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index 7805f40cfbf8a3..dabd82f5f54e3e 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -2,7 +2,7 @@ module.exports = promisify; -function promisify (fn) { +function promisify (fn, single) { Object.defineProperty(inner, 'name', { writable: false, enumerable: false, @@ -21,21 +21,18 @@ function promisify (fn) { resolve = _resolve; reject = _reject; }); - try { - fn.apply(this, [...arguments, function (err) { - if (err) { - return reject(err); - } - switch (arguments.length) { - case 0: return resolve(); - case 1: return resolve(); - case 2: return resolve(arguments[1]); - } - return resolve(resolver(...[...arguments].slice(1))); - }]); - } catch (err) { - reject(err); - } + + fn.apply(this, [...arguments, single ? resolve : function (err) { + if (err) { + return reject(err); + } + switch (arguments.length) { + case 0: return resolve(); + case 1: return resolve(); + case 2: return resolve(arguments[1]); + } + return resolve([...arguments].slice(1)); + }]); return promise; } } diff --git a/test/fixtures/test-fs-readfile-error.js b/test/fixtures/test-fs-readfile-error.js deleted file mode 100644 index 0638d01ac7655a..00000000000000 --- a/test/fixtures/test-fs-readfile-error.js +++ /dev/null @@ -1 +0,0 @@ -require('fs').readFile('/'); // throws EISDIR diff --git a/test/parallel/test-fs-access.js b/test/parallel/test-fs-access.js index aafc32192d374a..bc961d2b9fc004 100644 --- a/test/parallel/test-fs-access.js +++ b/test/parallel/test-fs-access.js @@ -94,14 +94,6 @@ assert.throws(function() { fs.access(100, fs.F_OK, function(err) {}); }, /path must be a string/); -assert.throws(function() { - fs.access(__filename, fs.F_OK); -}, /"callback" argument must be a function/); - -assert.throws(function() { - fs.access(__filename, fs.F_OK, {}); -}, /"callback" argument must be a function/); - assert.doesNotThrow(function() { fs.accessSync(__filename); }); diff --git a/test/parallel/test-fs-readfile-error.js b/test/parallel/test-fs-readfile-error.js deleted file mode 100644 index c61449a2db9b56..00000000000000 --- a/test/parallel/test-fs-readfile-error.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; -var common = require('../common'); -var assert = require('assert'); -var exec = require('child_process').exec; -var path = require('path'); - -// `fs.readFile('/')` does not fail on FreeBSD, because you can open and read -// the directory there. -if (process.platform === 'freebsd') { - console.log('1..0 # Skipped: platform not supported.'); - return; -} - -var callbacks = 0; - -function test(env, cb) { - var filename = path.join(common.fixturesDir, 'test-fs-readfile-error.js'); - var execPath = '"' + process.execPath + '" "' + filename + '"'; - var options = { env: Object.assign(process.env, env) }; - exec(execPath, options, function(err, stdout, stderr) { - assert(err); - assert.equal(stdout, ''); - assert.notEqual(stderr, ''); - cb('' + stderr); - }); -} - -test({ NODE_DEBUG: '' }, function(data) { - assert(/EISDIR/.test(data)); - assert(!/test-fs-readfile-error/.test(data)); - callbacks++; -}); - -test({ NODE_DEBUG: 'fs' }, function(data) { - assert(/EISDIR/.test(data)); - assert(/test-fs-readfile-error/.test(data)); - callbacks++; -}); - -process.on('exit', function() { - assert.equal(callbacks, 2); -}); From 68837b87097d3ca814ecb38e4226d38b9324a957 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 20:42:22 -0800 Subject: [PATCH 10/33] dns: promisify --- lib/dns.js | 17 +++++++++-------- test/parallel/test-dns-regress-7070.js | 1 - test/parallel/test-dns.js | 8 -------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/lib/dns.js b/lib/dns.js index 6c9795384fbf51..f63285cdeefbcc 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -2,6 +2,7 @@ const net = require('net'); const util = require('util'); +const promisify = require('internal/promisify'); const cares = process.binding('cares_wrap'); const uv = process.binding('uv'); @@ -103,7 +104,7 @@ function onlookupall(err, addresses) { // Easy DNS A/AAAA look up // lookup(hostname, [options,] callback) -exports.lookup = function lookup(hostname, options, callback) { +exports.lookup = promisify(function lookup(hostname, options, callback) { var hints = 0; var family = -1; var all = false; @@ -170,7 +171,7 @@ exports.lookup = function lookup(hostname, options, callback) { callback.immediately = true; return req; -}; +}); function onlookupservice(err, host, service) { @@ -182,7 +183,7 @@ function onlookupservice(err, host, service) { // lookupService(address, port, callback) -exports.lookupService = function(host, port, callback) { +exports.lookupService = promisify(function(host, port, callback) { if (arguments.length !== 3) throw new Error('Invalid arguments'); @@ -205,7 +206,7 @@ exports.lookupService = function(host, port, callback) { callback.immediately = true; return req; -}; +}); function onresolve(err, result) { @@ -219,7 +220,7 @@ function onresolve(err, result) { function resolver(bindingName) { var binding = cares[bindingName]; - return function query(name, callback) { + return promisify(function query(name, callback) { if (typeof name !== 'string') { throw new Error('"name" argument must be a string'); } else if (typeof callback !== 'function') { @@ -236,7 +237,7 @@ function resolver(bindingName) { if (err) throw errnoException(err, bindingName); callback.immediately = true; return req; - }; + }); } @@ -253,7 +254,7 @@ exports.resolveSoa = resolveMap.SOA = resolver('querySoa'); exports.reverse = resolveMap.PTR = resolver('getHostByAddr'); -exports.resolve = function(hostname, type_, callback_) { +exports.resolve = promisify(function(hostname, type_, callback_) { var resolver, callback; if (typeof type_ === 'string') { resolver = resolveMap[type_]; @@ -270,7 +271,7 @@ exports.resolve = function(hostname, type_, callback_) { } else { throw new Error('Unknown type "' + type_ + '"'); } -}; +}); exports.getServers = function() { diff --git a/test/parallel/test-dns-regress-7070.js b/test/parallel/test-dns-regress-7070.js index e696327d4d5f66..aca1c6c172c37a 100644 --- a/test/parallel/test-dns-regress-7070.js +++ b/test/parallel/test-dns-regress-7070.js @@ -5,4 +5,3 @@ var dns = require('dns'); // Should not raise assertion error. Issue #7070 assert.throws(function() { dns.resolveNs([]); }); // bad name -assert.throws(function() { dns.resolveNs(''); }); // bad callback diff --git a/test/parallel/test-dns.js b/test/parallel/test-dns.js index 84b74e022ab813..fbeac5852062f9 100644 --- a/test/parallel/test-dns.js +++ b/test/parallel/test-dns.js @@ -104,14 +104,6 @@ assert.throws(function() { noop); }); -assert.throws(function() { - dns.lookup('www.google.com'); -}, 'invalid arguments: callback must be passed'); - -assert.throws(function() { - dns.lookup('www.google.com', 4); -}, 'invalid arguments: callback must be passed'); - assert.doesNotThrow(function() { dns.lookup('www.google.com', 6, noop); }); From 3317652505017eca3346500e57c4804a528f536e Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 20:52:58 -0800 Subject: [PATCH 11/33] dgram: promisify .{bind,close,send} --- lib/dgram.js | 17 +++++++++-------- test/parallel/test-dgram-bind.js | 3 +-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/dgram.js b/lib/dgram.js index 279f59526974b9..aba51f3934daa0 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -1,6 +1,7 @@ 'use strict'; const assert = require('assert'); +const promisify = require('internal/promisify'); const Buffer = require('buffer').Buffer; const util = require('util'); const EventEmitter = require('events'); @@ -134,7 +135,7 @@ function replaceHandle(self, newHandle) { self._handle = newHandle; } -Socket.prototype.bind = function(port_ /*, address, callback*/) { +Socket.prototype.bind = promisify(function(port_ /*, address, callback*/) { var self = this; let port = port_; @@ -223,11 +224,11 @@ Socket.prototype.bind = function(port_ /*, address, callback*/) { }); return self; -}; +}); // thin wrapper around `send`, here for compatibility with dgram_legacy.js -Socket.prototype.sendto = function(buffer, +Socket.prototype.sendto = promisify(function(buffer, offset, length, port, @@ -240,7 +241,7 @@ Socket.prototype.sendto = function(buffer, throw new Error(this.type + ' sockets must send to port, address'); this.send(buffer, offset, length, port, address, callback); -}; +}); function sliceBuffer(buffer, offset, length) { @@ -286,7 +287,7 @@ function enqueue(self, toEnqueue) { } -Socket.prototype.send = function(buffer, +Socket.prototype.send = promisify(function(buffer, offset, length, port, @@ -339,7 +340,7 @@ Socket.prototype.send = function(buffer, self._handle.lookup(address, function afterDns(ex, ip) { doSend(ex, self, ip, buffer, address, port, callback); }); -}; +}); function doSend(ex, self, ip, buffer, address, port, callback) { @@ -384,7 +385,7 @@ function afterSend(err, sent) { } -Socket.prototype.close = function(callback) { +Socket.prototype.close = promisify(function(callback) { if (typeof callback === 'function') this.on('close', callback); this._healthCheck(); @@ -395,7 +396,7 @@ Socket.prototype.close = function(callback) { process.nextTick(socketCloseNT, self); return this; -}; +}); function socketCloseNT(self) { diff --git a/test/parallel/test-dgram-bind.js b/test/parallel/test-dgram-bind.js index 0bca97fb294f79..12ca6519baf04e 100644 --- a/test/parallel/test-dgram-bind.js +++ b/test/parallel/test-dgram-bind.js @@ -9,6 +9,5 @@ socket.on('listening', function() { socket.close(); }); -var result = socket.bind(); // should not throw - +var result = socket.bind(() => {}); // should not throw assert.strictEqual(result, socket); // should have returned itself From 846bd493bcb0e611b9dc10c2145457d84764c071 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 20:59:03 -0800 Subject: [PATCH 12/33] doc,dgram: add promise notes to dgram docs --- doc/api/dgram.markdown | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/api/dgram.markdown b/doc/api/dgram.markdown index 8408321c384e07..d75463c38c9ac4 100644 --- a/doc/api/dgram.markdown +++ b/doc/api/dgram.markdown @@ -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 @@ -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]) @@ -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 @@ -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 @@ -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 @@ -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 From d90b42c69fc0bad866f5f5e435b2dcbc5d7c7054 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 22:33:21 -0800 Subject: [PATCH 13/33] internal: add hooks for specifying different promise resolver --- lib/http.js | 10 ++++++---- lib/https.js | 10 ++++++---- lib/internal/callbackify.js | 21 +++++++++------------ lib/internal/promises.js | 23 +++++++++++++++++++++++ lib/internal/promisify.js | 7 +++++-- node.gyp | 1 + 6 files changed, 50 insertions(+), 22 deletions(-) create mode 100644 lib/internal/promises.js diff --git a/lib/http.js b/lib/http.js index cab8c0dbfb0fdc..36876836c08bad 100644 --- a/lib/http.js +++ b/lib/http.js @@ -3,6 +3,8 @@ const util = require('util'); const internalUtil = require('internal/util'); const EventEmitter = require('events'); +const promises = require('internal/promises'); +const BuiltinPromise = Promise; exports.IncomingMessage = require('_http_incoming').IncomingMessage; @@ -33,9 +35,9 @@ exports.request = function(options, cb) { exports.requestAsync = function(options) { var resolveRequest = null; - const getResponse = new Promise((resolve) => { + const getResponse = promises.wrap(new BuiltinPromise((resolve) => { resolveRequest = resolve; - }); + })); const request = exports.request(options, (res) => { resolveRequest(res); }); @@ -54,10 +56,10 @@ exports.get = function(options, cb) { exports.getAsync = function(options) { var resolveRequest = null; var rejectRequest = null; - const getResponse = new Promise((resolve, reject) => { + const getResponse = promises.wrap(new BuiltinPromise((resolve, reject) => { resolveRequest = resolve; rejectRequest = reject; - }); + })); const request = exports.request(options, (res) => { resolveRequest(res); }); diff --git a/lib/https.js b/lib/https.js index a21b48b1ad358b..3783aa3031b35f 100644 --- a/lib/https.js +++ b/lib/https.js @@ -6,6 +6,8 @@ const http = require('http'); const util = require('util'); const inherits = util.inherits; const debug = util.debuglog('https'); +const promises = require('internal/promises'); +const BuiltinPromise = promises.Promise; function Server(opts, requestListener) { if (!(this instanceof Server)) return new Server(opts, requestListener); @@ -184,9 +186,9 @@ exports.request = function(options, cb) { exports.requestAsync = function(options) { var resolveRequest = null; - const getResponse = new Promise((resolve) => { + const getResponse = promises.wrap(new BuiltinPromise((resolve) => { resolveRequest = resolve; - }); + })); const request = exports.request(options, (res) => { resolveRequest(res); }); @@ -205,10 +207,10 @@ exports.get = function(options, cb) { exports.getAsync = function(options) { var resolveRequest = null; var rejectRequest = null; - const getResponse = new Promise((resolve, reject) => { + const getResponse = promises.wrap(new BuiltinPromise((resolve, reject) => { resolveRequest = resolve; rejectRequest = reject; - }); + })); const request = exports.request(options, (res) => { resolveRequest(res); }); diff --git a/lib/internal/callbackify.js b/lib/internal/callbackify.js index af07dc86f041ec..bfc2e5c7a4fd90 100644 --- a/lib/internal/callbackify.js +++ b/lib/internal/callbackify.js @@ -2,7 +2,10 @@ module.exports = callbackify; -function callbackify (fn) { +const promises = require('internal/promises'); +const BuiltinPromise = promises.Promise; + +function callbackify(fn) { Object.defineProperty(inner, 'name', { writable: false, enumerable: false, @@ -11,21 +14,15 @@ function callbackify (fn) { }); return inner; - function inner () { - var resolve = null; - var reject = null; + function inner() { const cb = arguments[arguments.length - 1]; if (typeof cb !== 'function') { - cb = err => { throw err; }; + cb = (err) => { throw err; }; } - const promise = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; - }); try { - Promise.resolve(fn.apply(this, arguments)).then( - result => cb(null, result), - error => cb(error) + BuiltinPromise.resolve(fn.apply(this, arguments)).then( + (result) => cb(null, result), + (error) => cb(error) ); } catch (err) { cb(err); diff --git a/lib/internal/promises.js b/lib/internal/promises.js new file mode 100644 index 00000000000000..527685024531d0 --- /dev/null +++ b/lib/internal/promises.js @@ -0,0 +1,23 @@ +'use strict'; + +module.exports = { + wrap, + setResolver, + Promise +}; + +const defaultResolver = { + resolve(xs) { + return xs; + } +}; + +var resolver = defaultResolver; + +function wrap(promise) { + return resolver.resolve(promise); +} + +function setResolver(wrapper) { + resolver = wrapper || defaultResolver; +} diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index dabd82f5f54e3e..d76eec3363f0f7 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -2,6 +2,9 @@ module.exports = promisify; +const promises = require('internal/promises'); +const BuiltinPromise = promises.Promise; + function promisify (fn, single) { Object.defineProperty(inner, 'name', { writable: false, @@ -17,10 +20,10 @@ function promisify (fn, single) { } var resolve = null; var reject = null; - const promise = new Promise((_resolve, _reject) => { + const promise = promises.wrap(new BuiltinPromise((_resolve, _reject) => { resolve = _resolve; reject = _reject; - }); + })); fn.apply(this, [...arguments, single ? resolve : function (err) { if (err) { diff --git a/node.gyp b/node.gyp index 0bb533fb9b3f3a..8650b3eb421177 100644 --- a/node.gyp +++ b/node.gyp @@ -74,6 +74,7 @@ 'lib/internal/cluster.js', 'lib/internal/freelist.js', 'lib/internal/linkedlist.js', + 'lib/internal/promises.js', 'lib/internal/promisify.js', 'lib/internal/net.js', 'lib/internal/module.js', From adbe35210cd9bd82011a5eab65c615b66295b83d Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 22:47:01 -0800 Subject: [PATCH 14/33] crypto: promisify pbkdf2, add randomBytesAsync --- doc/api/crypto.markdown | 32 +++++++++++++++++++++++++---- lib/crypto.js | 9 +++++--- test/parallel/test-crypto-pbkdf2.js | 6 ++---- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/doc/api/crypto.markdown b/doc/api/crypto.markdown index 9d42cebb2a49f8..1d19311a86b5bb 100644 --- a/doc/api/crypto.markdown +++ b/doc/api/crypto.markdown @@ -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`. @@ -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 @@ -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, @@ -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')}` + ); }); ``` @@ -1241,7 +1244,8 @@ 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. @@ -1249,6 +1253,24 @@ 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). @@ -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 @@ -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 diff --git a/lib/crypto.js b/lib/crypto.js index 4b0539406e5ac6..c717cc5ba90be7 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -21,6 +21,7 @@ const stream = require('stream'); const util = require('util'); const internalUtil = require('internal/util'); const LazyTransform = require('internal/streams/lazy_transform'); +const promisify = require('internal/promisify'); const DH_GENERATOR = 2; @@ -536,7 +537,7 @@ const pbkdf2DeprecationWarning = ' a digest is deprecated. Please specify a digest'); -exports.pbkdf2 = function(password, +exports.pbkdf2 = promisify(function(password, salt, iterations, keylen, @@ -552,7 +553,7 @@ exports.pbkdf2 = function(password, throw new Error('No callback provided to pbkdf2'); return pbkdf2(password, salt, iterations, keylen, digest, callback); -}; +}); exports.pbkdf2Sync = function(password, salt, iterations, keylen, digest) { @@ -628,8 +629,10 @@ exports.setEngine = function setEngine(id, flags) { }; exports.randomBytes = exports.pseudoRandomBytes = randomBytes; - +exports.randomBytesAsync = +exports.pseudoRandomBytesAsync = promisify(randomBytes); exports.rng = exports.prng = randomBytes; +exports.rngAsync = exports.prngAsync = promisify(randomBytes); exports.getCiphers = function() { return filterDuplicates(getCiphers()); diff --git a/test/parallel/test-crypto-pbkdf2.js b/test/parallel/test-crypto-pbkdf2.js index cbea3dae4c0d04..85e8f79b102334 100644 --- a/test/parallel/test-crypto-pbkdf2.js +++ b/test/parallel/test-crypto-pbkdf2.js @@ -55,10 +55,8 @@ function ondone(err, key) { assert.equal(key.toString('hex'), expected); } -// Error path should not leak memory (check with valgrind). -assert.throws(function() { - crypto.pbkdf2('password', 'salt', 1, 20, null); -}); +// Should return promise if no callback given +assert.ok(crypto.pbkdf2('password', 'salt', 1, 20, null) instanceof Promise); // Should not work with Infinity key length assert.throws(function() { From 697ce018bb1b73872b4f48872c7fec3b4fd71060 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 23:21:48 -0800 Subject: [PATCH 15/33] child_process,cluster: promisify .send(), add exec{File,}Async --- doc/api/child_process.markdown | 59 +++++++++++++++---- lib/child_process.js | 8 +++ lib/cluster.js | 5 +- lib/internal/child_process.js | 9 ++- ...test-child-process-send-returns-boolean.js | 13 ++-- 5 files changed, 73 insertions(+), 21 deletions(-) diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index 65d7be99c6fad4..0b27d408243497 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -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 @@ -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 @@ -755,7 +784,7 @@ grep.stdin.end(); * `message` {Object} * `sendHandle` {Handle object} * `callback` {Function} -* Return: Boolean +* Return: Boolean | Promise 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 @@ -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); @@ -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 @@ -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 diff --git a/lib/child_process.js b/lib/child_process.js index e682aed5aede07..c1398ca0f7e6f7 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -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'); @@ -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 = { @@ -288,6 +292,10 @@ exports.execFile = function(file /*, args, options, callback*/) { return child; }; + +exports.execFileAsync = promisify(exports.execFile); + + var _deprecatedCustomFds = internalUtil.deprecate(function(options) { options.stdio = options.customFds.map(function(fd) { return fd === -1 ? 'pipe' : fd; diff --git a/lib/cluster.js b/lib/cluster.js index c8bf658d5b32a8..ef92b2a40488fe 100644 --- a/lib/cluster.js +++ b/lib/cluster.js @@ -1,5 +1,6 @@ 'use strict'; +const promisify = require('internal/promisify'); const EventEmitter = require('events'); const assert = require('assert'); const dgram = require('dgram'); @@ -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')); @@ -405,7 +406,7 @@ function masterInit() { } } if (cb) intercom.once('disconnect', cb); - }; + }); Worker.prototype.disconnect = function() { this.suicide = true; diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index 6487de2e9efa08..236f4746f53d82 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -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'); @@ -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); } @@ -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); diff --git a/test/parallel/test-child-process-send-returns-boolean.js b/test/parallel/test-child-process-send-returns-boolean.js index 73d4454087ec8e..d76cd8b1811edb 100644 --- a/test/parallel/test-child-process-send-returns-boolean.js +++ b/test/parallel/test-child-process-send-returns-boolean.js @@ -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'] }; @@ -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() { +} From f36b62d848b864f172d31d02dfcf585ce47e003a Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Sun, 31 Jan 2016 23:39:29 -0800 Subject: [PATCH 16/33] add process.setPromiseImplementation --- lib/internal/promises.js | 36 ++++++++++++++++++++++++++++++++++-- src/node.js | 1 + 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/internal/promises.js b/lib/internal/promises.js index 527685024531d0..3e9433acfa5ff0 100644 --- a/lib/internal/promises.js +++ b/lib/internal/promises.js @@ -2,8 +2,8 @@ module.exports = { wrap, - setResolver, - Promise + Promise, + setup }; const defaultResolver = { @@ -21,3 +21,35 @@ function wrap(promise) { function setResolver(wrapper) { resolver = wrapper || defaultResolver; } + +function setup(process) { + var implSet = false; + var where = null + process.setPromiseImplementation = function setImpl(impl) { + const assert = require('assert'); + const util = require('internal/util'); + if (!implSet) { + implSet = true; + where = new Error('setPromiseImplementation'); + process.setPromiseImplementation = util.deprecate( + setImpl, + 'setPromiseImplementation has already been called for this process:\n' + + where.stack + '\n' + ); + } + assert( + (typeof impl === 'object' || typeof impl === 'function') && impl, + 'implementation should be a truthy object' + ); + assert( + typeof impl.resolve === 'function', + 'implementation should have a .resolve method' + ); + const test = impl.resolve(); + assert( + test && typeof test.then === 'function', + '.resolve should return a thenable' + ); + setResolver(impl); + }; +} diff --git a/src/node.js b/src/node.js index 1f29386a87714f..86e74a415db099 100644 --- a/src/node.js +++ b/src/node.js @@ -47,6 +47,7 @@ startup.processRawDebug(); process.argv[0] = process.execPath; + NativeModule.require('internal/promises').setup(process); // There are various modes that Node can run in. The most common two // are running from a script and running the REPL - but there are a few From 082fa0801032e98650f8c67e113b701f8195508d Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Mon, 1 Feb 2016 00:02:56 -0800 Subject: [PATCH 17/33] process: add setPromiseImplementation API --- doc/api/process.markdown | 23 ++++++++ lib/internal/promises.js | 20 +++---- .../test-process-set-promises-impl.js | 58 +++++++++++++++++++ 3 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 test/parallel/test-process-set-promises-impl.js diff --git a/doc/api/process.markdown b/doc/api/process.markdown index 4f739c0dc70567..27fe864671cbaf 100644 --- a/doc/api/process.markdown +++ b/doc/api/process.markdown @@ -908,6 +908,29 @@ need to be root or have the `CAP_SETGID` capability. The list can contain group IDs, group names or both. +## process.setPromiseImplementation(impl) + +Sets the [`Promise`][def-promises] implementation for the process. All promises +returned by core APIs will be resolved by this implementation, giving third +party promise implementations an opportunity to cast native promises into a +different type. + +**Only top-level applications should use this API.** If this API is called more +than once, subsequent calls will issue deprecation warnings so authors can +track down offending modules. + +```javascript +process.setPromiseImplementation(require('bluebird')); + +const fs = require('fs'); + +fs.readFile('/usr/share/dict/words', 'utf8').then((words) => { + console.log(`a picture is worth ${words.split('\n').length} words.`); +}, (err) => { + console.error('there are no words.'); +}); +``` + ## process.setuid(id) Note: this function is only available on POSIX platforms (i.e. not Windows, diff --git a/lib/internal/promises.js b/lib/internal/promises.js index 3e9433acfa5ff0..6a89e13711bc2b 100644 --- a/lib/internal/promises.js +++ b/lib/internal/promises.js @@ -24,19 +24,10 @@ function setResolver(wrapper) { function setup(process) { var implSet = false; - var where = null + var where = null; process.setPromiseImplementation = function setImpl(impl) { const assert = require('assert'); const util = require('internal/util'); - if (!implSet) { - implSet = true; - where = new Error('setPromiseImplementation'); - process.setPromiseImplementation = util.deprecate( - setImpl, - 'setPromiseImplementation has already been called for this process:\n' + - where.stack + '\n' - ); - } assert( (typeof impl === 'object' || typeof impl === 'function') && impl, 'implementation should be a truthy object' @@ -50,6 +41,15 @@ function setup(process) { test && typeof test.then === 'function', '.resolve should return a thenable' ); + if (!implSet) { + implSet = true; + where = new Error('setPromiseImplementation'); + process.setPromiseImplementation = util.deprecate( + setImpl, + 'setPromiseImplementation has already been called for this process:\n' + + where.stack + '\n' + ); + } setResolver(impl); }; } diff --git a/test/parallel/test-process-set-promises-impl.js b/test/parallel/test-process-set-promises-impl.js new file mode 100644 index 00000000000000..9e0d9e87314088 --- /dev/null +++ b/test/parallel/test-process-set-promises-impl.js @@ -0,0 +1,58 @@ +'use strict'; + +require('../common'); + +const fs = require('fs'); +var assert = require('assert'); + +assert.throws(() => { + process.setPromiseImplementation(null); +}); + +assert.throws(() => { + process.setPromiseImplementation('ok'); +}); + +assert.throws(() => { + process.setPromiseImplementation({}); +}); + +assert.throws(() => { + process.setPromiseImplementation({ + resolve() { + } + }); +}); + +assert.throws(() => { + process.setPromiseImplementation({ + resolve() { + return {}; + } + }); +}); + +process.setPromiseImplementation({ + resolve(promise) { + return { + hello: 'world', + then(fn0, fn1) { + return promise.then(fn0, fn1); + } + }; + } +}); + +// affects old requires: +const p = fs.readFile(__filename, 'utf8'); +p.then((lines) => { + assert.ok(lines.indexOf("'use strict'") !== -1); +}); +assert.equal(p.hello, 'world'); + +// affects new requires: +const r = require('crypto').randomBytesAsync(16); +r.then((bytes) => { + assert.ok(Buffer.isBuffer(bytes)); +}); +assert.equal(r.hello, 'world'); From 54e30012a579a434f3f1c474dc7b818c30d48ab8 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Mon, 1 Feb 2016 01:12:11 -0800 Subject: [PATCH 18/33] tls,net: connectAsync should resolve to Socket --- lib/_tls_wrap.js | 7 +++++-- lib/net.js | 23 +++++++++-------------- lib/tls.js | 2 ++ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index dd293121cbc782..d4e0f9fc6f9f71 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -1000,8 +1000,11 @@ exports.connect = function(/* [port, host], options, cb */) { requestOCSP: options.requestOCSP }); - if (cb) - socket.once('secureConnect', cb); + if (cb) { + socket.once('secureConnect', () => { + cb.call(socket, null, socket); + }); + } if (!options.socket) { var connect_opt; diff --git a/lib/net.js b/lib/net.js index 08da6052e7ec6d..b78258c9872a25 100644 --- a/lib/net.js +++ b/lib/net.js @@ -66,12 +66,7 @@ exports.connect = exports.createConnection = function() { return Socket.prototype.connect.apply(s, args); }; -exports.connectAsync = promisify(function() { - var args = normalizeConnectArgs(arguments); - debug('createConnection', args); - var s = new Socket(args[0]); - return Socket.prototype.connect.apply(s, args); -}); +exports.connectAsync = promisify(exports.connect); // Returns an array [options] or [options, cb] // It is the same as the argument of Socket.prototype.connect(). @@ -886,7 +881,6 @@ Socket.prototype.connect = function(options, cb) { this._sockname = null; } - var self = this; var pipe = !!options.path; debug('pipe', pipe, options.path); @@ -896,21 +890,22 @@ Socket.prototype.connect = function(options, cb) { } if (typeof cb === 'function') { - self.once('connect', cb); + this.once('connect', () => { + return cb.call(this, null, this); + }); } this._unrefTimer(); - self._connecting = true; - self.writable = true; + this._connecting = true; + this.writable = true; if (pipe) { - connect(self, options.path); - + connect(this, options.path); } else { - lookupAndConnect(self, options); + lookupAndConnect(this, options); } - return self; + return this; }; diff --git a/lib/tls.js b/lib/tls.js index 245afb6bdc7561..26e4f2d376a074 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -5,6 +5,7 @@ const url = require('url'); const binding = process.binding('crypto'); const Buffer = require('buffer').Buffer; const constants = require('constants'); +const promisify = require('internal/promisify'); // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations // every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more @@ -237,4 +238,5 @@ exports.TLSSocket = require('_tls_wrap').TLSSocket; exports.Server = require('_tls_wrap').Server; exports.createServer = require('_tls_wrap').createServer; exports.connect = require('_tls_wrap').connect; +exports.connectAsync = promisify(require('_tls_wrap').connect); exports.createSecurePair = require('_tls_legacy').createSecurePair; From 014fb3eddcea80b8be65318f304580127c1a1258 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Mon, 1 Feb 2016 16:09:52 -0800 Subject: [PATCH 19/33] lib: repromisify --- lib/cluster.js | 5 +- lib/crypto.js | 5 +- lib/dgram.js | 21 ++++--- lib/dns.js | 30 +++++++--- lib/fs.js | 150 +++++++++++++++++++++++++++++------------------- lib/net.js | 5 +- lib/readline.js | 5 +- lib/repl.js | 5 +- lib/zlib.js | 35 ++++++----- 9 files changed, 160 insertions(+), 101 deletions(-) diff --git a/lib/cluster.js b/lib/cluster.js index ef92b2a40488fe..0f953861aaade4 100644 --- a/lib/cluster.js +++ b/lib/cluster.js @@ -394,7 +394,7 @@ function masterInit() { cluster.emit('fork', worker); } - cluster.disconnect = promisify(function(cb) { + cluster.disconnect = function(cb) { var workers = Object.keys(cluster.workers); if (workers.length === 0) { process.nextTick(intercom.emit.bind(intercom, 'disconnect')); @@ -406,7 +406,8 @@ function masterInit() { } } if (cb) intercom.once('disconnect', cb); - }); + }; + cluster.disconnectAsync = promisify(cluster.disconnect); Worker.prototype.disconnect = function() { this.suicide = true; diff --git a/lib/crypto.js b/lib/crypto.js index c717cc5ba90be7..9fdbc1b5aca8d0 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -537,7 +537,7 @@ const pbkdf2DeprecationWarning = ' a digest is deprecated. Please specify a digest'); -exports.pbkdf2 = promisify(function(password, +exports.pbkdf2 = function(password, salt, iterations, keylen, @@ -553,7 +553,8 @@ exports.pbkdf2 = promisify(function(password, throw new Error('No callback provided to pbkdf2'); return pbkdf2(password, salt, iterations, keylen, digest, callback); -}); +}; +exports.pbkdf2Async = promisify(exports.pbkdf2); exports.pbkdf2Sync = function(password, salt, iterations, keylen, digest) { diff --git a/lib/dgram.js b/lib/dgram.js index aba51f3934daa0..42f8e068bd01da 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -135,7 +135,7 @@ function replaceHandle(self, newHandle) { self._handle = newHandle; } -Socket.prototype.bind = promisify(function(port_ /*, address, callback*/) { +Socket.prototype.bind = function(port_ /*, address, callback*/) { var self = this; let port = port_; @@ -224,11 +224,12 @@ Socket.prototype.bind = promisify(function(port_ /*, address, callback*/) { }); return self; -}); +}; +Socket.prototype.bindAsync = promisify(Socket.prototype.bind); // thin wrapper around `send`, here for compatibility with dgram_legacy.js -Socket.prototype.sendto = promisify(function(buffer, +Socket.prototype.sendto = function(buffer, offset, length, port, @@ -241,7 +242,8 @@ Socket.prototype.sendto = promisify(function(buffer, throw new Error(this.type + ' sockets must send to port, address'); this.send(buffer, offset, length, port, address, callback); -}); +}; +Socket.prototype.sendtoAsync = promisify(Socket.prototype.sendto); function sliceBuffer(buffer, offset, length) { @@ -287,7 +289,7 @@ function enqueue(self, toEnqueue) { } -Socket.prototype.send = promisify(function(buffer, +Socket.prototype.send = function(buffer, offset, length, port, @@ -340,8 +342,8 @@ Socket.prototype.send = promisify(function(buffer, self._handle.lookup(address, function afterDns(ex, ip) { doSend(ex, self, ip, buffer, address, port, callback); }); -}); - +}; +Socket.prototype.sendAsync = promisify(Socket.prototype.send); function doSend(ex, self, ip, buffer, address, port, callback) { if (ex) { @@ -385,7 +387,7 @@ function afterSend(err, sent) { } -Socket.prototype.close = promisify(function(callback) { +Socket.prototype.close = function(callback) { if (typeof callback === 'function') this.on('close', callback); this._healthCheck(); @@ -396,7 +398,8 @@ Socket.prototype.close = promisify(function(callback) { process.nextTick(socketCloseNT, self); return this; -}); +}; +Socket.prototype.closeAsync = promisify(Socket.prototype.close); function socketCloseNT(self) { diff --git a/lib/dns.js b/lib/dns.js index f63285cdeefbcc..df11897bd0dd59 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -104,7 +104,7 @@ function onlookupall(err, addresses) { // Easy DNS A/AAAA look up // lookup(hostname, [options,] callback) -exports.lookup = promisify(function lookup(hostname, options, callback) { +exports.lookup = function lookup(hostname, options, callback) { var hints = 0; var family = -1; var all = false; @@ -171,7 +171,8 @@ exports.lookup = promisify(function lookup(hostname, options, callback) { callback.immediately = true; return req; -}); +}; +exports.lookupAsync = promisify(exports.lookup); function onlookupservice(err, host, service) { @@ -183,7 +184,7 @@ function onlookupservice(err, host, service) { // lookupService(address, port, callback) -exports.lookupService = promisify(function(host, port, callback) { +exports.lookupService = function(host, port, callback) { if (arguments.length !== 3) throw new Error('Invalid arguments'); @@ -206,7 +207,8 @@ exports.lookupService = promisify(function(host, port, callback) { callback.immediately = true; return req; -}); +}; +exports.lookupServiceAsync = promisify(exports.lookupService); function onresolve(err, result) { @@ -220,7 +222,7 @@ function onresolve(err, result) { function resolver(bindingName) { var binding = cares[bindingName]; - return promisify(function query(name, callback) { + return function query(name, callback) { if (typeof name !== 'string') { throw new Error('"name" argument must be a string'); } else if (typeof callback !== 'function') { @@ -237,7 +239,7 @@ function resolver(bindingName) { if (err) throw errnoException(err, bindingName); callback.immediately = true; return req; - }); + }; } @@ -253,8 +255,19 @@ exports.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr'); exports.resolveSoa = resolveMap.SOA = resolver('querySoa'); exports.reverse = resolveMap.PTR = resolver('getHostByAddr'); +exports.resolve4Async = promisify(resolver('queryA')); +exports.resolve6Async = promisify(resolver('queryAaaa')); +exports.resolveCnameAsync = promisify(resolver('queryCname')); +exports.resolveMxAsync = promisify(resolver('queryMx')); +exports.resolveNsAsync = promisify(resolver('queryNs')); +exports.resolveTxtAsync = promisify(resolver('queryTxt')); +exports.resolveSrvAsync = promisify(resolver('querySrv')); +exports.resolveNaptrAsync = promisify(resolver('queryNaptr')); +exports.resolveSoaAsync = promisify(resolver('querySoa')); +exports.reverseAsync = promisify(resolver('getHostByAddr')); + -exports.resolve = promisify(function(hostname, type_, callback_) { +exports.resolve = function(hostname, type_, callback_) { var resolver, callback; if (typeof type_ === 'string') { resolver = resolveMap[type_]; @@ -271,7 +284,8 @@ exports.resolve = promisify(function(hostname, type_, callback_) { } else { throw new Error('Unknown type "' + type_ + '"'); } -}); +}; +exports.resolveAsync = promisify(exports.resolve); exports.getServers = function() { diff --git a/lib/fs.js b/lib/fs.js index a58e3e977fbe9e..9e31c49c725a8c 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -180,7 +180,7 @@ fs.Stats.prototype.isSocket = function() { }); }); -fs.access = promisify(function(path, mode, callback) { +fs.access = function(path, mode, callback) { if (typeof mode === 'function') { callback = mode; mode = fs.F_OK; @@ -195,7 +195,8 @@ fs.access = promisify(function(path, mode, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.access(pathModule._makeLong(path), mode, req); -}); +}; +fs.accessAsync = promisify(fs.access); fs.accessSync = function(path, mode) { nullCheck(path); @@ -208,7 +209,7 @@ fs.accessSync = function(path, mode) { binding.access(pathModule._makeLong(path), mode); }; -fs.exists = promisify(function(path, callback) { +fs.exists = function(path, callback) { if (!nullCheck(path, cb)) return; var req = new FSReqWrap(); req.oncomplete = cb; @@ -216,7 +217,8 @@ fs.exists = promisify(function(path, callback) { function cb(err, stats) { if (callback) callback(err ? false : true); } -}, 1); +}; +fs.existsAsync = promisify(fs.exists, 1); fs.existsSync = function(path) { try { @@ -228,7 +230,7 @@ fs.existsSync = function(path) { } }; -fs.readFile = promisify(function(path, options, callback_) { +fs.readFile = function(path, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); if (!options || typeof options === 'function') { @@ -264,7 +266,8 @@ fs.readFile = promisify(function(path, options, callback_) { stringToFlags(flag), 0o666, req); -}); +}; +fs.readFileAsync = promisify(fs.readFile); const kReadFileBufferLength = 8 * 1024; @@ -544,11 +547,12 @@ Object.defineProperty(exports, '_stringToFlags', { // Yes, the follow could be easily DRYed up but I provide the explicit // list to make the arguments clear. -fs.close = promisify(function(fd, callback) { +fs.close = function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.close(fd, req); -}); +}; +fs.closeAsync = promisify(fs.close); fs.closeSync = function(fd) { return binding.close(fd); @@ -564,7 +568,7 @@ function modeNum(m, def) { return undefined; } -fs.open = promisify(function(path, flags, mode, callback_) { +fs.open = function(path, flags, mode, callback_) { var callback = makeCallback(arguments[arguments.length - 1]); mode = modeNum(mode, 0o666); @@ -577,7 +581,8 @@ fs.open = promisify(function(path, flags, mode, callback_) { stringToFlags(flags), mode, req); -}); +}; +fs.openAsync = promisify(fs.open); fs.openSync = function(path, flags, mode) { mode = modeNum(mode, 0o666); @@ -727,7 +732,7 @@ fs.writeSync = function(fd, buffer, offset, length, position) { return binding.writeString(fd, buffer, offset, length, position); }; -fs.rename = promisify(function(oldPath, newPath, callback) { +fs.rename = function(oldPath, newPath, callback) { callback = makeCallback(callback); if (!nullCheck(oldPath, callback)) return; if (!nullCheck(newPath, callback)) return; @@ -736,7 +741,8 @@ fs.rename = promisify(function(oldPath, newPath, callback) { binding.rename(pathModule._makeLong(oldPath), pathModule._makeLong(newPath), req); -}); +}; +fs.renameAsync = promisify(fs.rename); fs.renameSync = function(oldPath, newPath) { nullCheck(oldPath); @@ -789,7 +795,7 @@ fs.truncateSync = function(path, len) { return ret; }; -fs.ftruncate = promisify(function(fd, len, callback) { +fs.ftruncate = function(fd, len, callback) { if (typeof len === 'function') { callback = len; len = 0; @@ -799,7 +805,8 @@ fs.ftruncate = promisify(function(fd, len, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.ftruncate(fd, len, req); -}); +}; +fs.ftruncateAsync = promisify(fs.ftruncate); fs.ftruncateSync = function(fd, len) { if (len === undefined) { @@ -808,40 +815,43 @@ fs.ftruncateSync = function(fd, len) { return binding.ftruncate(fd, len); }; -fs.rmdir = promisify(function(path, callback) { +fs.rmdir = function(path, callback) { callback = maybeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.rmdir(pathModule._makeLong(path), req); -}); +}; +fs.rmdirAsync = promisify(fs.rmdir); fs.rmdirSync = function(path) { nullCheck(path); return binding.rmdir(pathModule._makeLong(path)); }; -fs.fdatasync = promisify(function(fd, callback) { +fs.fdatasync = function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fdatasync(fd, req); -}); +}; +fs.fdatasyncAsync = promisify(fs.fdatasync); fs.fdatasyncSync = function(fd) { return binding.fdatasync(fd); }; -fs.fsync = promisify(function(fd, callback) { +fs.fsync = function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fsync(fd, req); -}); +}; +fs.fsyncAsync = promisify(fs.fsync); fs.fsyncSync = function(fd) { return binding.fsync(fd); }; -fs.mkdir = promisify(function(path, mode, callback) { +fs.mkdir = function(path, mode, callback) { if (typeof mode === 'function') callback = mode; callback = makeCallback(callback); if (!nullCheck(path, callback)) return; @@ -850,7 +860,8 @@ fs.mkdir = promisify(function(path, mode, callback) { binding.mkdir(pathModule._makeLong(path), modeNum(mode, 0o777), req); -}); +}; +fs.mkdirAsync = promisify(fs.mkdir); fs.mkdirSync = function(path, mode) { nullCheck(path); @@ -858,40 +869,44 @@ fs.mkdirSync = function(path, mode) { modeNum(mode, 0o777)); }; -fs.readdir = promisify(function(path, callback) { +fs.readdir = function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.readdir(pathModule._makeLong(path), req); -}); +}; +fs.readdirAsync = promisify(fs.readdir); fs.readdirSync = function(path) { nullCheck(path); return binding.readdir(pathModule._makeLong(path)); }; -fs.fstat = promisify(function(fd, callback) { +fs.fstat = function(fd, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fstat(fd, req); -}); +}; +fs.fstatAsync = promisify(fs.fstat); -fs.lstat = promisify(function(path, callback) { +fs.lstat = function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.lstat(pathModule._makeLong(path), req); -}); +}; +fs.lstatAsync = promisify(fs.lstat); -fs.stat = promisify(function(path, callback) { +fs.stat = function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.stat(pathModule._makeLong(path), req); -}); +}; +fs.statAsync = promisify(fs.stat); fs.fstatSync = function(fd) { return binding.fstat(fd); @@ -907,13 +922,14 @@ fs.statSync = function(path) { return binding.stat(pathModule._makeLong(path)); }; -fs.readlink = promisify(function(path, callback) { +fs.readlink = function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.readlink(pathModule._makeLong(path), req); -}); +}; +fs.readlinkAsync = promisify(fs.readlink); fs.readlinkSync = function(path) { nullCheck(path); @@ -935,7 +951,7 @@ function preprocessSymlinkDestination(path, type, linkPath) { } } -fs.symlink = promisify(function(target, path, type_, callback_) { +fs.symlink = function(target, path, type_, callback_) { var type = (typeof type_ === 'string' ? type_ : null); var callback = makeCallback(arguments[arguments.length - 1]); @@ -949,7 +965,8 @@ fs.symlink = promisify(function(target, path, type_, callback_) { pathModule._makeLong(path), type, req); -}); +}; +fs.symlinkAsync = promisify(fs.symlink); fs.symlinkSync = function(target, path, type) { type = (typeof type === 'string' ? type : null); @@ -962,7 +979,7 @@ fs.symlinkSync = function(target, path, type) { type); }; -fs.link = promisify(function(srcpath, dstpath, callback) { +fs.link = function(srcpath, dstpath, callback) { callback = makeCallback(callback); if (!nullCheck(srcpath, callback)) return; if (!nullCheck(dstpath, callback)) return; @@ -973,7 +990,8 @@ fs.link = promisify(function(srcpath, dstpath, callback) { binding.link(pathModule._makeLong(srcpath), pathModule._makeLong(dstpath), req); -}); +}; +fs.linkAsync = promisify(fs.link); fs.linkSync = function(srcpath, dstpath) { nullCheck(srcpath); @@ -982,31 +1000,33 @@ fs.linkSync = function(srcpath, dstpath) { pathModule._makeLong(dstpath)); }; -fs.unlink = promisify(function(path, callback) { +fs.unlink = function(path, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.unlink(pathModule._makeLong(path), req); -}); +}; +fs.unlinkAsync = promisify(fs.unlink); fs.unlinkSync = function(path) { nullCheck(path); return binding.unlink(pathModule._makeLong(path)); }; -fs.fchmod = promisify(function(fd, mode, callback) { +fs.fchmod = function(fd, mode, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fchmod(fd, modeNum(mode), req); -}); +}; +fs.fchmodAsync = promisify(fs.fchmod); fs.fchmodSync = function(fd, mode) { return binding.fchmod(fd, modeNum(mode)); }; if (constants.hasOwnProperty('O_SYMLINK')) { - fs.lchmod = promisify(function(path, mode, callback) { + fs.lchmod = function(path, mode, callback) { callback = maybeCallback(callback); fs.open(path, constants.O_WRONLY | constants.O_SYMLINK, function(err, fd) { if (err) { @@ -1021,7 +1041,8 @@ if (constants.hasOwnProperty('O_SYMLINK')) { }); }); }); - }); + }; + fs.lchmodAsync = promisify(fs.lchmod); fs.lchmodSync = function(path, mode) { var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); @@ -1045,7 +1066,7 @@ if (constants.hasOwnProperty('O_SYMLINK')) { } -fs.chmod = promisify(function(path, mode, callback) { +fs.chmod = function(path, mode, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); @@ -1053,7 +1074,8 @@ fs.chmod = promisify(function(path, mode, callback) { binding.chmod(pathModule._makeLong(path), modeNum(mode), req); -}); +}; +fs.chmodAsync = promisify(fs.chmod); fs.chmodSync = function(path, mode) { nullCheck(path); @@ -1061,7 +1083,7 @@ fs.chmodSync = function(path, mode) { }; if (constants.hasOwnProperty('O_SYMLINK')) { - fs.lchown = promisify(function(path, uid, gid, callback) { + fs.lchown = function(path, uid, gid, callback) { callback = maybeCallback(callback); fs.open(path, constants.O_WRONLY | constants.O_SYMLINK, function(err, fd) { if (err) { @@ -1070,7 +1092,8 @@ if (constants.hasOwnProperty('O_SYMLINK')) { } fs.fchown(fd, uid, gid, callback); }); - }); + }; + fs.lchownAsync = promisify(fs.lchown); fs.lchownSync = function(path, uid, gid) { var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); @@ -1078,23 +1101,25 @@ if (constants.hasOwnProperty('O_SYMLINK')) { }; } -fs.fchown = promisify(function(fd, uid, gid, callback) { +fs.fchown = function(fd, uid, gid, callback) { var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.fchown(fd, uid, gid, req); -}); +}; +fs.fchownAsync = promisify(fs.fchown); fs.fchownSync = function(fd, uid, gid) { return binding.fchown(fd, uid, gid); }; -fs.chown = promisify(function(path, uid, gid, callback) { +fs.chown = function(path, uid, gid, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); req.oncomplete = callback; binding.chown(pathModule._makeLong(path), uid, gid, req); -}); +}; +fs.chownAsync = promisify(fs.chown); fs.chownSync = function(path, uid, gid) { nullCheck(path); @@ -1122,7 +1147,7 @@ function toUnixTimestamp(time) { // exported for unit tests, not for public consumption fs._toUnixTimestamp = toUnixTimestamp; -fs.utimes = promisify(function(path, atime, mtime, callback) { +fs.utimes = function(path, atime, mtime, callback) { callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); @@ -1131,7 +1156,8 @@ fs.utimes = promisify(function(path, atime, mtime, callback) { toUnixTimestamp(atime), toUnixTimestamp(mtime), req); -}); +}; +fs.utimesAsync = promisify(fs.utimes); fs.utimesSync = function(path, atime, mtime) { nullCheck(path); @@ -1140,13 +1166,14 @@ fs.utimesSync = function(path, atime, mtime) { binding.utimes(pathModule._makeLong(path), atime, mtime); }; -fs.futimes = promisify(function(fd, atime, mtime, callback) { +fs.futimes = function(fd, atime, mtime, callback) { atime = toUnixTimestamp(atime); mtime = toUnixTimestamp(mtime); var req = new FSReqWrap(); req.oncomplete = makeCallback(callback); binding.futimes(fd, atime, mtime, req); -}); +}; +fs.futimesAsync = promisify(fs.futimes); fs.futimesSync = function(fd, atime, mtime) { atime = toUnixTimestamp(atime); @@ -1186,7 +1213,7 @@ function writeAll(fd, isUserFd, buffer, offset, length, position, callback_) { }); } -fs.writeFile = promisify(function(path, data, options, callback_) { +fs.writeFile = function(path, data, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); if (!options || typeof options === 'function') { @@ -1221,7 +1248,8 @@ fs.writeFile = promisify(function(path, data, options, callback_) { writeAll(fd, isUserFd, buffer, 0, buffer.length, position, callback); } -}); +}; +fs.writeFileAsync = promisify(fs.writeFile); fs.writeFileSync = function(path, data, options) { if (!options) { @@ -1258,7 +1286,7 @@ fs.writeFileSync = function(path, data, options) { } }; -fs.appendFile = promisify(function(path, data, options, callback_) { +fs.appendFile = function(path, data, options, callback_) { var callback = maybeCallback(arguments[arguments.length - 1]); if (!options || typeof options === 'function') { @@ -1277,7 +1305,8 @@ fs.appendFile = promisify(function(path, data, options, callback_) { options.flag = 'a'; fs.writeFile(path, data, options, callback); -}); +}; +fs.appendFileAsync = promisify(fs.appendFile); fs.appendFileSync = function(path, data, options) { if (!options) { @@ -1571,7 +1600,7 @@ fs.realpathSync = function realpathSync(p, cache) { }; -fs.realpath = promisify(function realpath(p, cache, cb) { +fs.realpath = function realpath(p, cache, cb) { if (typeof cb !== 'function') { cb = maybeCallback(cache); cache = null; @@ -1691,7 +1720,8 @@ fs.realpath = promisify(function realpath(p, cache, cb) { p = pathModule.resolve(resolvedLink, p.slice(pos)); start(); } -}); +}; +fs.realpathAsync = promisify(fs.realpath); var pool; diff --git a/lib/net.js b/lib/net.js index b78258c9872a25..ac78b03d520736 100644 --- a/lib/net.js +++ b/lib/net.js @@ -301,7 +301,7 @@ Socket.prototype.listen = function() { }; -Socket.prototype.setTimeout = promisify(function(msecs, callback) { +Socket.prototype.setTimeout = function(msecs, callback) { if (typeof msecs === 'function') { callback = msecs; msecs = 0; @@ -319,7 +319,8 @@ Socket.prototype.setTimeout = promisify(function(msecs, callback) { } } return this; -}); +}; +Socket.prototype.setTimeoutAsync = promisify(Socket.prototype.setTimeout); Socket.prototype._onTimeout = function() { diff --git a/lib/readline.js b/lib/readline.js index ee81ffb36387e1..9413e557726092 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -194,7 +194,7 @@ Interface.prototype.prompt = function(preserveCursor) { }; -Interface.prototype.question = promisify(function(query, cb) { +Interface.prototype.question = function(query, cb) { if (typeof cb === 'function') { if (this._questionCallback) { this.prompt(); @@ -205,7 +205,8 @@ Interface.prototype.question = promisify(function(query, cb) { this.prompt(); } } -}); +}; +Interface.prototype.questionAsync = promisify(Interface.prototype.question); Interface.prototype._onLine = function(line) { diff --git a/lib/repl.js b/lib/repl.js index 48fa6f5862eddc..24821050ad97dd 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -627,7 +627,7 @@ function filteredOwnPropertyNames(obj) { // // Warning: This eval's code like "foo.bar.baz", so it will run property // getter code. -REPLServer.prototype.complete = promisify(function(line, callback) { +REPLServer.prototype.complete = function(line, callback) { // There may be local variables to evaluate, try a nested REPL if (this.bufferedCommand !== undefined && this.bufferedCommand.length) { // Get a new array of inputed lines @@ -885,7 +885,8 @@ REPLServer.prototype.complete = promisify(function(line, callback) { callback(null, [completions || [], completeOn]); } -}); +}; +REPLServer.prototype.completeAsync = promisify(REPLServer.prototype.complete); /** diff --git a/lib/zlib.js b/lib/zlib.js index 3bb1b124767c8c..37faea0d1d305d 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -104,85 +104,92 @@ exports.createUnzip = function(o) { // Convenience methods. // compress/decompress a string or buffer in one step. -exports.deflate = promisify(function(buffer, opts, callback) { +exports.deflate = function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Deflate(opts), buffer, callback); -}); +}; +exports.deflateAsync = promisify(exports.deflate); exports.deflateSync = function(buffer, opts) { return zlibBufferSync(new Deflate(opts), buffer); }; -exports.gzip = promisify(function(buffer, opts, callback) { +exports.gzip = function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Gzip(opts), buffer, callback); -}); +}; +exports.gzipAsync = promisify(exports.gzip); exports.gzipSync = function(buffer, opts) { return zlibBufferSync(new Gzip(opts), buffer); }; -exports.deflateRaw = promisify(function(buffer, opts, callback) { +exports.deflateRaw = function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new DeflateRaw(opts), buffer, callback); -}); +}; +exports.deflateRawAsync = promisify(exports.deflateRaw); exports.deflateRawSync = function(buffer, opts) { return zlibBufferSync(new DeflateRaw(opts), buffer); }; -exports.unzip = promisify(function(buffer, opts, callback) { +exports.unzip = function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Unzip(opts), buffer, callback); -}); +}; +exports.unzipAsync = promisify(exports.unzip); exports.unzipSync = function(buffer, opts) { return zlibBufferSync(new Unzip(opts), buffer); }; -exports.inflate = promisify(function(buffer, opts, callback) { +exports.inflate = function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Inflate(opts), buffer, callback); -}); +}; +exports.inflateAsync = promisify(exports.inflate); exports.inflateSync = function(buffer, opts) { return zlibBufferSync(new Inflate(opts), buffer); }; -exports.gunzip = promisify(function(buffer, opts, callback) { +exports.gunzip = function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new Gunzip(opts), buffer, callback); -}); +}; +exports.gunzipAsync = promisify(exports.gunzip); exports.gunzipSync = function(buffer, opts) { return zlibBufferSync(new Gunzip(opts), buffer); }; -exports.inflateRaw = promisify(function(buffer, opts, callback) { +exports.inflateRaw = function(buffer, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } return zlibBuffer(new InflateRaw(opts), buffer, callback); -}); +}; +exports.inflateRawAsync = promisify(exports.inflateRaw); exports.inflateRawSync = function(buffer, opts) { return zlibBufferSync(new InflateRaw(opts), buffer); From 15a42c17668eb79e1f09cc0959bd0763e218a54f Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Mon, 1 Feb 2016 19:53:44 -0800 Subject: [PATCH 20/33] repromisify --- lib/internal/child_process.js | 5 +- test/fixtures/test-fs-readfile-error.js | 1 + ...test-child-process-send-returns-boolean.js | 13 ++--- test/parallel/test-crypto-pbkdf2.js | 6 +- test/parallel/test-dgram-bind.js | 3 +- test/parallel/test-dns-regress-7070.js | 1 + test/parallel/test-dns.js | 8 +++ test/parallel/test-fs-access.js | 8 +++ test/parallel/test-fs-readfile-error.js | 42 ++++++++++++++ test/parallel/test-http-set-timeout.js | 17 ++---- .../test-process-set-promises-impl.js | 58 ------------------- test/parallel/test-tls-request-timeout.js | 11 ++-- 12 files changed, 82 insertions(+), 91 deletions(-) create mode 100644 test/fixtures/test-fs-readfile-error.js create mode 100644 test/parallel/test-fs-readfile-error.js delete mode 100644 test/parallel/test-process-set-promises-impl.js diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index 236f4746f53d82..101b1533def34a 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -499,7 +499,7 @@ function setupChannel(target, channel) { }); }); - target.send = promisify(function(message, handle, callback) { + target.send = function(message, handle, callback) { if (typeof handle === 'function') { callback = handle; handle = undefined; @@ -518,7 +518,8 @@ function setupChannel(target, channel) { this.emit('error', ex); // FIXME(bnoordhuis) Defer to next tick. } return false; - }); + }; + target.sendAsync = promisify(target.send); target._send = function(message, handle, swallowErrors, callback) { assert(this.connected || this._channel); diff --git a/test/fixtures/test-fs-readfile-error.js b/test/fixtures/test-fs-readfile-error.js new file mode 100644 index 00000000000000..0638d01ac7655a --- /dev/null +++ b/test/fixtures/test-fs-readfile-error.js @@ -0,0 +1 @@ +require('fs').readFile('/'); // throws EISDIR diff --git a/test/parallel/test-child-process-send-returns-boolean.js b/test/parallel/test-child-process-send-returns-boolean.js index d76cd8b1811edb..73d4454087ec8e 100644 --- a/test/parallel/test-child-process-send-returns-boolean.js +++ b/test/parallel/test-child-process-send-returns-boolean.js @@ -10,7 +10,7 @@ const emptyFile = path.join(common.fixturesDir, 'empty.js'); const n = fork(emptyFile); -const rv = n.send({ hello: 'world' }, noop); +const rv = n.send({ hello: 'world' }); assert.strictEqual(rv, true); const spawnOptions = { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] }; @@ -22,11 +22,8 @@ s.on('exit', function() { net.createServer(common.fail).listen(common.PORT, function() { handle = this._handle; - 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); + 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); }); - -function noop() { -} diff --git a/test/parallel/test-crypto-pbkdf2.js b/test/parallel/test-crypto-pbkdf2.js index 85e8f79b102334..cbea3dae4c0d04 100644 --- a/test/parallel/test-crypto-pbkdf2.js +++ b/test/parallel/test-crypto-pbkdf2.js @@ -55,8 +55,10 @@ function ondone(err, key) { assert.equal(key.toString('hex'), expected); } -// Should return promise if no callback given -assert.ok(crypto.pbkdf2('password', 'salt', 1, 20, null) instanceof Promise); +// Error path should not leak memory (check with valgrind). +assert.throws(function() { + crypto.pbkdf2('password', 'salt', 1, 20, null); +}); // Should not work with Infinity key length assert.throws(function() { diff --git a/test/parallel/test-dgram-bind.js b/test/parallel/test-dgram-bind.js index 12ca6519baf04e..0bca97fb294f79 100644 --- a/test/parallel/test-dgram-bind.js +++ b/test/parallel/test-dgram-bind.js @@ -9,5 +9,6 @@ socket.on('listening', function() { socket.close(); }); -var result = socket.bind(() => {}); // should not throw +var result = socket.bind(); // should not throw + assert.strictEqual(result, socket); // should have returned itself diff --git a/test/parallel/test-dns-regress-7070.js b/test/parallel/test-dns-regress-7070.js index aca1c6c172c37a..e696327d4d5f66 100644 --- a/test/parallel/test-dns-regress-7070.js +++ b/test/parallel/test-dns-regress-7070.js @@ -5,3 +5,4 @@ var dns = require('dns'); // Should not raise assertion error. Issue #7070 assert.throws(function() { dns.resolveNs([]); }); // bad name +assert.throws(function() { dns.resolveNs(''); }); // bad callback diff --git a/test/parallel/test-dns.js b/test/parallel/test-dns.js index fbeac5852062f9..84b74e022ab813 100644 --- a/test/parallel/test-dns.js +++ b/test/parallel/test-dns.js @@ -104,6 +104,14 @@ assert.throws(function() { noop); }); +assert.throws(function() { + dns.lookup('www.google.com'); +}, 'invalid arguments: callback must be passed'); + +assert.throws(function() { + dns.lookup('www.google.com', 4); +}, 'invalid arguments: callback must be passed'); + assert.doesNotThrow(function() { dns.lookup('www.google.com', 6, noop); }); diff --git a/test/parallel/test-fs-access.js b/test/parallel/test-fs-access.js index bc961d2b9fc004..aafc32192d374a 100644 --- a/test/parallel/test-fs-access.js +++ b/test/parallel/test-fs-access.js @@ -94,6 +94,14 @@ assert.throws(function() { fs.access(100, fs.F_OK, function(err) {}); }, /path must be a string/); +assert.throws(function() { + fs.access(__filename, fs.F_OK); +}, /"callback" argument must be a function/); + +assert.throws(function() { + fs.access(__filename, fs.F_OK, {}); +}, /"callback" argument must be a function/); + assert.doesNotThrow(function() { fs.accessSync(__filename); }); diff --git a/test/parallel/test-fs-readfile-error.js b/test/parallel/test-fs-readfile-error.js new file mode 100644 index 00000000000000..c61449a2db9b56 --- /dev/null +++ b/test/parallel/test-fs-readfile-error.js @@ -0,0 +1,42 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); +var exec = require('child_process').exec; +var path = require('path'); + +// `fs.readFile('/')` does not fail on FreeBSD, because you can open and read +// the directory there. +if (process.platform === 'freebsd') { + console.log('1..0 # Skipped: platform not supported.'); + return; +} + +var callbacks = 0; + +function test(env, cb) { + var filename = path.join(common.fixturesDir, 'test-fs-readfile-error.js'); + var execPath = '"' + process.execPath + '" "' + filename + '"'; + var options = { env: Object.assign(process.env, env) }; + exec(execPath, options, function(err, stdout, stderr) { + assert(err); + assert.equal(stdout, ''); + assert.notEqual(stderr, ''); + cb('' + stderr); + }); +} + +test({ NODE_DEBUG: '' }, function(data) { + assert(/EISDIR/.test(data)); + assert(!/test-fs-readfile-error/.test(data)); + callbacks++; +}); + +test({ NODE_DEBUG: 'fs' }, function(data) { + assert(/EISDIR/.test(data)); + assert(/test-fs-readfile-error/.test(data)); + callbacks++; +}); + +process.on('exit', function() { + assert.equal(callbacks, 2); +}); diff --git a/test/parallel/test-http-set-timeout.js b/test/parallel/test-http-set-timeout.js index 8d77bae74b3feb..9bda60b2226443 100644 --- a/test/parallel/test-http-set-timeout.js +++ b/test/parallel/test-http-set-timeout.js @@ -1,27 +1,18 @@ 'use strict'; var common = require('../common'); +var assert = require('assert'); var http = require('http'); +var net = require('net'); var server = http.createServer(function(req, res) { console.log('got request. setting 1 second timeout'); var s = req.connection.setTimeout(500); - var pending = 2; - s.then(() => { - if (!--pending) { - closeServer(); - } - }); + assert.ok(s instanceof net.Socket); req.connection.on('timeout', function() { - if (!--pending) { - closeServer(); - } - }); - - function closeServer() { req.connection.destroy(); console.error('TIMEOUT'); server.close(); - } + }); }); server.listen(common.PORT, function() { diff --git a/test/parallel/test-process-set-promises-impl.js b/test/parallel/test-process-set-promises-impl.js deleted file mode 100644 index 9e0d9e87314088..00000000000000 --- a/test/parallel/test-process-set-promises-impl.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -require('../common'); - -const fs = require('fs'); -var assert = require('assert'); - -assert.throws(() => { - process.setPromiseImplementation(null); -}); - -assert.throws(() => { - process.setPromiseImplementation('ok'); -}); - -assert.throws(() => { - process.setPromiseImplementation({}); -}); - -assert.throws(() => { - process.setPromiseImplementation({ - resolve() { - } - }); -}); - -assert.throws(() => { - process.setPromiseImplementation({ - resolve() { - return {}; - } - }); -}); - -process.setPromiseImplementation({ - resolve(promise) { - return { - hello: 'world', - then(fn0, fn1) { - return promise.then(fn0, fn1); - } - }; - } -}); - -// affects old requires: -const p = fs.readFile(__filename, 'utf8'); -p.then((lines) => { - assert.ok(lines.indexOf("'use strict'") !== -1); -}); -assert.equal(p.hello, 'world'); - -// affects new requires: -const r = require('crypto').randomBytesAsync(16); -r.then((bytes) => { - assert.ok(Buffer.isBuffer(bytes)); -}); -assert.equal(r.hello, 'world'); diff --git a/test/parallel/test-tls-request-timeout.js b/test/parallel/test-tls-request-timeout.js index 24a7ef6e9c0c87..0db2a613afc9e4 100644 --- a/test/parallel/test-tls-request-timeout.js +++ b/test/parallel/test-tls-request-timeout.js @@ -10,7 +10,7 @@ var tls = require('tls'); var fs = require('fs'); -var hadTimeout = 0; +var hadTimeout = false; var options = { key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), @@ -19,13 +19,10 @@ var options = { var server = tls.Server(options, function(socket) { var s = socket.setTimeout(100); - - s.then(() => { - ++hadTimeout; - }); + assert.ok(s instanceof tls.TLSSocket); socket.on('timeout', function(err) { - ++hadTimeout; + hadTimeout = true; socket.end(); server.close(); }); @@ -39,5 +36,5 @@ server.listen(common.PORT, function() { }); process.on('exit', function() { - assert.equal(hadTimeout, 2); + assert.ok(hadTimeout); }); From 8586b10cb5bc99a64d9c5d19e7fff2a90f50a96c Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Mon, 1 Feb 2016 23:06:33 -0800 Subject: [PATCH 21/33] src,lib: move back to native promises --- lib/http.js | 10 +++---- lib/https.js | 10 +++---- lib/internal/callbackify.js | 21 +++++--------- lib/internal/promises.js | 55 ------------------------------------- lib/internal/promisify.js | 30 ++++++++++---------- node.gyp | 1 - src/node.js | 1 - 7 files changed, 31 insertions(+), 97 deletions(-) delete mode 100644 lib/internal/promises.js diff --git a/lib/http.js b/lib/http.js index 36876836c08bad..cab8c0dbfb0fdc 100644 --- a/lib/http.js +++ b/lib/http.js @@ -3,8 +3,6 @@ const util = require('util'); const internalUtil = require('internal/util'); const EventEmitter = require('events'); -const promises = require('internal/promises'); -const BuiltinPromise = Promise; exports.IncomingMessage = require('_http_incoming').IncomingMessage; @@ -35,9 +33,9 @@ exports.request = function(options, cb) { exports.requestAsync = function(options) { var resolveRequest = null; - const getResponse = promises.wrap(new BuiltinPromise((resolve) => { + const getResponse = new Promise((resolve) => { resolveRequest = resolve; - })); + }); const request = exports.request(options, (res) => { resolveRequest(res); }); @@ -56,10 +54,10 @@ exports.get = function(options, cb) { exports.getAsync = function(options) { var resolveRequest = null; var rejectRequest = null; - const getResponse = promises.wrap(new BuiltinPromise((resolve, reject) => { + const getResponse = new Promise((resolve, reject) => { resolveRequest = resolve; rejectRequest = reject; - })); + }); const request = exports.request(options, (res) => { resolveRequest(res); }); diff --git a/lib/https.js b/lib/https.js index 3783aa3031b35f..a21b48b1ad358b 100644 --- a/lib/https.js +++ b/lib/https.js @@ -6,8 +6,6 @@ const http = require('http'); const util = require('util'); const inherits = util.inherits; const debug = util.debuglog('https'); -const promises = require('internal/promises'); -const BuiltinPromise = promises.Promise; function Server(opts, requestListener) { if (!(this instanceof Server)) return new Server(opts, requestListener); @@ -186,9 +184,9 @@ exports.request = function(options, cb) { exports.requestAsync = function(options) { var resolveRequest = null; - const getResponse = promises.wrap(new BuiltinPromise((resolve) => { + const getResponse = new Promise((resolve) => { resolveRequest = resolve; - })); + }); const request = exports.request(options, (res) => { resolveRequest(res); }); @@ -207,10 +205,10 @@ exports.get = function(options, cb) { exports.getAsync = function(options) { var resolveRequest = null; var rejectRequest = null; - const getResponse = promises.wrap(new BuiltinPromise((resolve, reject) => { + const getResponse = new Promise((resolve, reject) => { resolveRequest = resolve; rejectRequest = reject; - })); + }); const request = exports.request(options, (res) => { resolveRequest(res); }); diff --git a/lib/internal/callbackify.js b/lib/internal/callbackify.js index bfc2e5c7a4fd90..3da4cc923579e7 100644 --- a/lib/internal/callbackify.js +++ b/lib/internal/callbackify.js @@ -2,14 +2,8 @@ module.exports = callbackify; -const promises = require('internal/promises'); -const BuiltinPromise = promises.Promise; - function callbackify(fn) { Object.defineProperty(inner, 'name', { - writable: false, - enumerable: false, - configurable: true, value: fn.name + 'Callback' }); return inner; @@ -19,13 +13,12 @@ function callbackify(fn) { if (typeof cb !== 'function') { cb = (err) => { throw err; }; } - try { - BuiltinPromise.resolve(fn.apply(this, arguments)).then( - (result) => cb(null, result), - (error) => cb(error) - ); - } catch (err) { - cb(err); - } + + new Promise((resolve) => { + resolve(fn.apply(this, arguments)); + }).then( + (result) => cb(null, result), + (error) => cb(error) + ); } } diff --git a/lib/internal/promises.js b/lib/internal/promises.js deleted file mode 100644 index 6a89e13711bc2b..00000000000000 --- a/lib/internal/promises.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -module.exports = { - wrap, - Promise, - setup -}; - -const defaultResolver = { - resolve(xs) { - return xs; - } -}; - -var resolver = defaultResolver; - -function wrap(promise) { - return resolver.resolve(promise); -} - -function setResolver(wrapper) { - resolver = wrapper || defaultResolver; -} - -function setup(process) { - var implSet = false; - var where = null; - process.setPromiseImplementation = function setImpl(impl) { - const assert = require('assert'); - const util = require('internal/util'); - assert( - (typeof impl === 'object' || typeof impl === 'function') && impl, - 'implementation should be a truthy object' - ); - assert( - typeof impl.resolve === 'function', - 'implementation should have a .resolve method' - ); - const test = impl.resolve(); - assert( - test && typeof test.then === 'function', - '.resolve should return a thenable' - ); - if (!implSet) { - implSet = true; - where = new Error('setPromiseImplementation'); - process.setPromiseImplementation = util.deprecate( - setImpl, - 'setPromiseImplementation has already been called for this process:\n' + - where.stack + '\n' - ); - } - setResolver(impl); - }; -} diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index d76eec3363f0f7..8ee8c97212af0b 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -2,30 +2,33 @@ module.exports = promisify; -const promises = require('internal/promises'); -const BuiltinPromise = promises.Promise; - -function promisify (fn, single) { +function promisify(fn, single) { Object.defineProperty(inner, 'name', { - writable: false, - enumerable: false, - configurable: true, value: fn.name + 'Promisified' }); return inner; - function inner () { + function inner() { if (typeof arguments[arguments.length - 1] === 'function') { return fn.apply(this, arguments); } var resolve = null; var reject = null; - const promise = promises.wrap(new BuiltinPromise((_resolve, _reject) => { + const args = Array.from(arguments); + const promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; - })); + }); + + try { + args.push(single ? resolve : resolver); + fn.apply(this, args); + } catch (err) { + reject(err); + } + return promise; - fn.apply(this, [...arguments, single ? resolve : function (err) { + function resolver(err) { if (err) { return reject(err); } @@ -34,8 +37,7 @@ function promisify (fn, single) { case 1: return resolve(); case 2: return resolve(arguments[1]); } - return resolve([...arguments].slice(1)); - }]); - return promise; + return resolve(Array.from(arguments).slice(1)); + } } } diff --git a/node.gyp b/node.gyp index 8650b3eb421177..0bb533fb9b3f3a 100644 --- a/node.gyp +++ b/node.gyp @@ -74,7 +74,6 @@ 'lib/internal/cluster.js', 'lib/internal/freelist.js', 'lib/internal/linkedlist.js', - 'lib/internal/promises.js', 'lib/internal/promisify.js', 'lib/internal/net.js', 'lib/internal/module.js', diff --git a/src/node.js b/src/node.js index 86e74a415db099..1f29386a87714f 100644 --- a/src/node.js +++ b/src/node.js @@ -47,7 +47,6 @@ startup.processRawDebug(); process.argv[0] = process.execPath; - NativeModule.require('internal/promises').setup(process); // There are various modes that Node can run in. The most common two // are running from a script and running the REPL - but there are a few From 40ca55e54a27c21e11fa7cf7c7f49700e197f0fb Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 2 Feb 2016 12:57:11 -0800 Subject: [PATCH 22/33] domain,promise,src: add shim to make promises work with async_wrap + domains --- lib/domain.js | 13 +++ lib/internal/async_wrap.js | 84 +++++++++++++++++ lib/internal/promise.js | 94 +++++++++++++++++++ node.gyp | 2 + src/async-wrap.h | 1 + src/node.js | 1 + .../test-async-wrap-check-providers.js | 9 +- .../test-promise-domain-interaction.js | 40 ++++++++ 8 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 lib/internal/async_wrap.js create mode 100644 lib/internal/promise.js create mode 100644 test/parallel/test-promise-domain-interaction.js diff --git a/lib/domain.js b/lib/domain.js index 41b4859361d5f3..76e4813b96b360 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -7,6 +7,7 @@ const util = require('util'); const EventEmitter = require('events'); +const promise = require('internal/promise'); const inherits = util.inherits; // communicate with events module, but don't require that @@ -35,6 +36,18 @@ exports._stack = stack; // let the process know we're using domains const _domain_flag = process._setupDomainUse(_domain, stack); +promise.addDomainHooks({ + init (promise, parent) { + promise.domain = parent.domain; + }, + wrap (promise, func, context) { + if (promise.domain) { + return promise.domain.bind(func); + } + return func; + } +}); + exports.Domain = Domain; exports.create = exports.createDomain = function() { diff --git a/lib/internal/async_wrap.js b/lib/internal/async_wrap.js new file mode 100644 index 00000000000000..aecc7d5185f493 --- /dev/null +++ b/lib/internal/async_wrap.js @@ -0,0 +1,84 @@ +'use strict' + +const binding = process.binding('async_wrap'); +const promise = require('internal/promise'); +const assert = require('assert'); +var installedPromises = false; +var currentHooks = null; + +module.exports = { + Providers: binding.Providers, + setupHooks, + disable, + enable +}; + +function setupHooks (hooks) { + currentHooks = Object.assign({ + init: noop, + pre: noop, + post: noop, + destroy: noop + }, hooks); + assert(typeof currentHooks.init === 'function'); + assert(typeof currentHooks.pre === 'function'); + assert(typeof currentHooks.post === 'function'); + assert(typeof currentHooks.destroy === 'function'); + + return binding.setupHooks( + hooks.init, + hooks.pre, + hooks.post, + hooks.destroy + ); +} + +function disable () { + currentHooks = null; + return binding.disable(); +} + +function enable () { + if (!installedPromises) { + _installPromises(); + } + return binding.enable(); +} + +function _installPromises () { + promise.addAsyncHooks({ + init (promise) { + if (!currentHooks) { + return; + } + currentHooks.init.call(this, binding.Providers.PROMISE) + }, + wrap (promise, fn, ctxt) { + return function () { + try { + if (currentHooks) { + currentHooks.pre.call(ctxt.promise); + } + } finally { + } + try { + return fn.apply(this, arguments); + } finally { + if (currentHooks) { + try { + currentHooks.post.call(ctxt.promise); + } finally { + } + try { + currentHooks.destroy.call(ctxt.promise); + } finally { + } + } + } + } + } + }) +} + +function noop () { +} diff --git a/lib/internal/promise.js b/lib/internal/promise.js new file mode 100644 index 00000000000000..c0f839ae907b8e --- /dev/null +++ b/lib/internal/promise.js @@ -0,0 +1,94 @@ +'use strict'; + +// This module patches the global Promise before any other code +// has access to it. It does this so that promises can be instrumented +// with domains and async_wrap as expected. It is a temporary API - +// once V8 offers a way for us to interact with the MicrotaskQueue +// it can fall off. +module.exports = { + addAsyncHooks, + addDomainHooks +}; + +const BuiltinPromise = Promise; + +Promise = function Promise (resolver) { + const base = new BuiltinPromise(resolver); + domainInitHook(base, process); + asyncInitHook(base); + return base; +} + +// Copy class methods. +const props = [ + 'accept', + 'reject', + 'all', + 'resolve', + 'defer', + 'race' +]; + +props.forEach((xs) => { + Promise[xs] = BuiltinPromise[xs]; +}); + +// This is so folks doing `Promise.prototype.then.call` get the right +// function. +Promise.prototype = Object.assign(Promise.prototype, BuiltinPromise.prototype); + +const originalThen = BuiltinPromise.prototype.then; +BuiltinPromise.prototype.then = function (success, failure) { + // XXX(chrisdickinson): We use a context object because we've got to + // wrap the function _before_ we get the child promise; but the wrapped + // function has to know about the child promise so async_wrap works as + // expected. + const context = {promise: null}; + switch (arguments.length) { + case 2: + success = domainWrapHook(this, success); + success = asyncWrapHook(this, success, context); + failure = domainWrapHook(this, failure); + failure = asyncWrapHook(this, failure, context); + context.promise = originalThen.call(this, success, failure); + domainInitHook(context.promise, this); + asyncInitHook(context.promise); + break; + case 1: + success = domainWrapHook(this, success); + success = asyncWrapHook(this, success, context); + context.promise = originalThen.call(this, success); + domainInitHook(context.promise, this); + asyncInitHook(context.promise); + break; + case 0: + context.promise = originalThen.call(this); + break; + } + domainInitHook(context.promise, this); + asyncInitHook(context.promise); + return context.promise; +}; + +var domainInitHook = _noopInit; +var domainWrapHook = _noopWrap; +var asyncInitHook = _noopInit; +var asyncWrapHook = _noopWrap; + +function addAsyncHooks (hooks) { + asyncInitHook = hooks.init || asyncInitHook; + asyncWrapHook = hooks.wrap || asyncWrapHook; +} + +function addDomainHooks (hooks) { + domainInitHook = hooks.init || domainInitHook; + domainWrapHook = hooks.wrap || domainWrapHook; +} + +function _noopInit (promise) { + return; +} + +function _noopWrap (promise, func, context) { + return func; +} diff --git a/node.gyp b/node.gyp index 0bb533fb9b3f3a..ea1c0562ba4350 100644 --- a/node.gyp +++ b/node.gyp @@ -69,11 +69,13 @@ 'lib/v8.js', 'lib/vm.js', 'lib/zlib.js', + 'lib/internal/async_wrap.js', 'lib/internal/callbackify.js', 'lib/internal/child_process.js', 'lib/internal/cluster.js', 'lib/internal/freelist.js', 'lib/internal/linkedlist.js', + 'lib/internal/promise.js', 'lib/internal/promisify.js', 'lib/internal/net.js', 'lib/internal/module.js', diff --git a/src/async-wrap.h b/src/async-wrap.h index 5db29600bcd180..3f7a155415a867 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -21,6 +21,7 @@ namespace node { V(PIPEWRAP) \ V(PIPECONNECTWRAP) \ V(PROCESSWRAP) \ + V(PROMISE) \ V(QUERYWRAP) \ V(SHUTDOWNWRAP) \ V(SIGNALWRAP) \ diff --git a/src/node.js b/src/node.js index 1f29386a87714f..8728145e3a1998 100644 --- a/src/node.js +++ b/src/node.js @@ -44,6 +44,7 @@ if (process.argv[1] !== '--debug-agent') startup.processChannel(); + NativeModule.require('internal/promise'); startup.processRawDebug(); process.argv[0] = process.execPath; diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index 38302be2cd0b5d..8334554d8ff553 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -1,4 +1,5 @@ 'use strict'; +// Flags: --expose-internals const common = require('../common'); const assert = require('assert'); @@ -11,7 +12,7 @@ const tls = require('tls'); const zlib = require('zlib'); const ChildProcess = require('child_process').ChildProcess; const StreamWrap = require('_stream_wrap').StreamWrap; -const async_wrap = process.binding('async_wrap'); +const async_wrap = require('internal/async_wrap'); const pkeys = Object.keys(async_wrap.Providers); let keyList = pkeys.slice(); @@ -25,7 +26,7 @@ function init(id) { function noop() { } -async_wrap.setupHooks(init, noop, noop); +async_wrap.setupHooks({init}); async_wrap.enable(); @@ -49,6 +50,10 @@ crypto.randomBytes(1, noop); common.refreshTmpDir(); +new Promise((resolve) => { + resolve(); +}); + net.createServer(function(c) { c.end(); this.close(); diff --git a/test/parallel/test-promise-domain-interaction.js b/test/parallel/test-promise-domain-interaction.js new file mode 100644 index 00000000000000..c75148077635dd --- /dev/null +++ b/test/parallel/test-promise-domain-interaction.js @@ -0,0 +1,40 @@ +'use strict' + +const common = require('../common'); +const domain = require('domain'); +const assert = require('assert'); +const fs = require('fs'); +const d = domain.create() + +const acc = []; + +d.on('error', function(err) { + acc.push(err.message); +}); +d.run(function() { + const p = fs.openAsync(__filename, 'r') + + p.then(function(fd) { + fs.readFileAsync(fd, function() { + throw new Error('one'); + }); + return fd; + }, function() { + setTimeout(function () { + throw new Error('should not reach this point.') + }); + }).then(function(fd) { + setTimeout(function() { + throw new Error('two') + }); + return fd; + }).then(function(fd) { + return fs.closeAsync(fd); + }); +}); + +process.on('exit', function () { + assert.ok(acc.indexOf('one') !== -1); + assert.ok(acc.indexOf('two') !== -1); + assert.equal(acc.length, 2); +}) From 3928beb34d4fef169df2cf9ff32d2ed11627de19 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 2 Feb 2016 14:10:48 -0800 Subject: [PATCH 23/33] domain: promises capture process.domain at .then time --- lib/domain.js | 13 ++++-------- lib/internal/promise.js | 46 ++++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/lib/domain.js b/lib/domain.js index 76e4813b96b360..33f9f06049244b 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -36,16 +36,11 @@ exports._stack = stack; // let the process know we're using domains const _domain_flag = process._setupDomainUse(_domain, stack); -promise.addDomainHooks({ - init (promise, parent) { - promise.domain = parent.domain; - }, - wrap (promise, func, context) { - if (promise.domain) { - return promise.domain.bind(func); - } - return func; +promise.addDomainHook(function(func) { + if (process.domain) { + return process.domain.bind(func); } + return func; }); exports.Domain = Domain; diff --git a/lib/internal/promise.js b/lib/internal/promise.js index c0f839ae907b8e..31f7926eca6467 100644 --- a/lib/internal/promise.js +++ b/lib/internal/promise.js @@ -7,14 +7,13 @@ // it can fall off. module.exports = { addAsyncHooks, - addDomainHooks + addDomainHook }; const BuiltinPromise = Promise; Promise = function Promise (resolver) { const base = new BuiltinPromise(resolver); - domainInitHook(base, process); asyncInitHook(base); return base; } @@ -30,7 +29,11 @@ const props = [ ]; props.forEach((xs) => { - Promise[xs] = BuiltinPromise[xs]; + Object.defineProperty( + Promise, + xs, + Object.getOwnPropertyDescriptor(BuiltinPromise, xs) + ); }); // This is so folks doing `Promise.prototype.then.call` get the right @@ -46,49 +49,54 @@ BuiltinPromise.prototype.then = function (success, failure) { const context = {promise: null}; switch (arguments.length) { case 2: - success = domainWrapHook(this, success); - success = asyncWrapHook(this, success, context); - failure = domainWrapHook(this, failure); - failure = asyncWrapHook(this, failure, context); + if (typeof success === 'function') { + success = domainWrapHook(success); + success = asyncWrapHook(this, success, context); + } + if (typeof failure === 'function') { + failure = domainWrapHook(failure); + failure = asyncWrapHook(this, failure, context); + } context.promise = originalThen.call(this, success, failure); - domainInitHook(context.promise, this); asyncInitHook(context.promise); break; case 1: - success = domainWrapHook(this, success); - success = asyncWrapHook(this, success, context); + if (typeof success === 'function') { + success = domainWrapHook(success); + success = asyncWrapHook(this, success, context); + } context.promise = originalThen.call(this, success); - domainInitHook(context.promise, this); asyncInitHook(context.promise); break; case 0: context.promise = originalThen.call(this); break; } - domainInitHook(context.promise, this); asyncInitHook(context.promise); return context.promise; }; -var domainInitHook = _noopInit; -var domainWrapHook = _noopWrap; +var domainWrapHook = _noopDomainWrap; var asyncInitHook = _noopInit; -var asyncWrapHook = _noopWrap; +var asyncWrapHook = _noopAsyncWrap; function addAsyncHooks (hooks) { asyncInitHook = hooks.init || asyncInitHook; asyncWrapHook = hooks.wrap || asyncWrapHook; } -function addDomainHooks (hooks) { - domainInitHook = hooks.init || domainInitHook; - domainWrapHook = hooks.wrap || domainWrapHook; +function addDomainHook (hook) { + domainWrapHook = hook; } function _noopInit (promise) { return; } -function _noopWrap (promise, func, context) { +function _noopAsyncWrap (promise, func, context) { + return func; +} + +function _noopDomainWrap (func) { return func; } From fa725b3fff5b714535094f50d80b7c4a26c54c4a Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 2 Feb 2016 15:16:17 -0800 Subject: [PATCH 24/33] src: back asyncwrap integration out of this pr --- lib/internal/async_wrap.js | 84 ------------------- lib/internal/promise.js | 79 +++-------------- node.gyp | 1 - src/async-wrap.h | 1 - .../test-async-wrap-check-providers.js | 8 +- 5 files changed, 13 insertions(+), 160 deletions(-) delete mode 100644 lib/internal/async_wrap.js diff --git a/lib/internal/async_wrap.js b/lib/internal/async_wrap.js deleted file mode 100644 index aecc7d5185f493..00000000000000 --- a/lib/internal/async_wrap.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict' - -const binding = process.binding('async_wrap'); -const promise = require('internal/promise'); -const assert = require('assert'); -var installedPromises = false; -var currentHooks = null; - -module.exports = { - Providers: binding.Providers, - setupHooks, - disable, - enable -}; - -function setupHooks (hooks) { - currentHooks = Object.assign({ - init: noop, - pre: noop, - post: noop, - destroy: noop - }, hooks); - assert(typeof currentHooks.init === 'function'); - assert(typeof currentHooks.pre === 'function'); - assert(typeof currentHooks.post === 'function'); - assert(typeof currentHooks.destroy === 'function'); - - return binding.setupHooks( - hooks.init, - hooks.pre, - hooks.post, - hooks.destroy - ); -} - -function disable () { - currentHooks = null; - return binding.disable(); -} - -function enable () { - if (!installedPromises) { - _installPromises(); - } - return binding.enable(); -} - -function _installPromises () { - promise.addAsyncHooks({ - init (promise) { - if (!currentHooks) { - return; - } - currentHooks.init.call(this, binding.Providers.PROMISE) - }, - wrap (promise, fn, ctxt) { - return function () { - try { - if (currentHooks) { - currentHooks.pre.call(ctxt.promise); - } - } finally { - } - try { - return fn.apply(this, arguments); - } finally { - if (currentHooks) { - try { - currentHooks.post.call(ctxt.promise); - } finally { - } - try { - currentHooks.destroy.call(ctxt.promise); - } finally { - } - } - } - } - } - }) -} - -function noop () { -} diff --git a/lib/internal/promise.js b/lib/internal/promise.js index 31f7926eca6467..76acf4806eeea6 100644 --- a/lib/internal/promise.js +++ b/lib/internal/promise.js @@ -1,102 +1,45 @@ 'use strict'; -// This module patches the global Promise before any other code -// has access to it. It does this so that promises can be instrumented -// with domains and async_wrap as expected. It is a temporary API - -// once V8 offers a way for us to interact with the MicrotaskQueue -// it can fall off. +// This module patches the global Promise before any other code has access to +// it. It does this so that promises can be instrumented with domains as +// expected. It is a temporary API - once V8 offers a way for us to interact +// with the MicrotaskQueue it can fall off. module.exports = { - addAsyncHooks, addDomainHook }; -const BuiltinPromise = Promise; - -Promise = function Promise (resolver) { - const base = new BuiltinPromise(resolver); - asyncInitHook(base); - return base; -} - -// Copy class methods. -const props = [ - 'accept', - 'reject', - 'all', - 'resolve', - 'defer', - 'race' -]; - -props.forEach((xs) => { - Object.defineProperty( - Promise, - xs, - Object.getOwnPropertyDescriptor(BuiltinPromise, xs) - ); -}); - -// This is so folks doing `Promise.prototype.then.call` get the right -// function. -Promise.prototype = Object.assign(Promise.prototype, BuiltinPromise.prototype); - -const originalThen = BuiltinPromise.prototype.then; -BuiltinPromise.prototype.then = function (success, failure) { - // XXX(chrisdickinson): We use a context object because we've got to - // wrap the function _before_ we get the child promise; but the wrapped - // function has to know about the child promise so async_wrap works as - // expected. - const context = {promise: null}; +const originalThen = Promise.prototype.then; +Promise.prototype.then = function (success, failure) { + var promise; switch (arguments.length) { case 2: if (typeof success === 'function') { success = domainWrapHook(success); - success = asyncWrapHook(this, success, context); } if (typeof failure === 'function') { failure = domainWrapHook(failure); - failure = asyncWrapHook(this, failure, context); } - context.promise = originalThen.call(this, success, failure); - asyncInitHook(context.promise); + promise = originalThen.call(this, success, failure); break; case 1: if (typeof success === 'function') { success = domainWrapHook(success); - success = asyncWrapHook(this, success, context); } - context.promise = originalThen.call(this, success); - asyncInitHook(context.promise); + promise = originalThen.call(this, success); break; case 0: - context.promise = originalThen.call(this); + promise = originalThen.call(this); break; } - asyncInitHook(context.promise); - return context.promise; + return promise; }; var domainWrapHook = _noopDomainWrap; -var asyncInitHook = _noopInit; -var asyncWrapHook = _noopAsyncWrap; - -function addAsyncHooks (hooks) { - asyncInitHook = hooks.init || asyncInitHook; - asyncWrapHook = hooks.wrap || asyncWrapHook; -} function addDomainHook (hook) { domainWrapHook = hook; } -function _noopInit (promise) { - return; -} - -function _noopAsyncWrap (promise, func, context) { - return func; -} - function _noopDomainWrap (func) { return func; } diff --git a/node.gyp b/node.gyp index ea1c0562ba4350..9c0243e674bbd6 100644 --- a/node.gyp +++ b/node.gyp @@ -69,7 +69,6 @@ 'lib/v8.js', 'lib/vm.js', 'lib/zlib.js', - 'lib/internal/async_wrap.js', 'lib/internal/callbackify.js', 'lib/internal/child_process.js', 'lib/internal/cluster.js', diff --git a/src/async-wrap.h b/src/async-wrap.h index 3f7a155415a867..5db29600bcd180 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -21,7 +21,6 @@ namespace node { V(PIPEWRAP) \ V(PIPECONNECTWRAP) \ V(PROCESSWRAP) \ - V(PROMISE) \ V(QUERYWRAP) \ V(SHUTDOWNWRAP) \ V(SIGNALWRAP) \ diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index 8334554d8ff553..5f15f28df9b5c1 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -12,7 +12,7 @@ const tls = require('tls'); const zlib = require('zlib'); const ChildProcess = require('child_process').ChildProcess; const StreamWrap = require('_stream_wrap').StreamWrap; -const async_wrap = require('internal/async_wrap'); +const async_wrap = process.binding('async_wrap'); const pkeys = Object.keys(async_wrap.Providers); let keyList = pkeys.slice(); @@ -26,7 +26,7 @@ function init(id) { function noop() { } -async_wrap.setupHooks({init}); +async_wrap.setupHooks(init, noop, noop); async_wrap.enable(); @@ -50,10 +50,6 @@ crypto.randomBytes(1, noop); common.refreshTmpDir(); -new Promise((resolve) => { - resolve(); -}); - net.createServer(function(c) { c.end(); this.close(); From 0528d74b1bf1d2a549d0f72f6bc774f4b09ffe40 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 2 Feb 2016 16:22:33 -0800 Subject: [PATCH 25/33] internal: lint --- lib/internal/promise.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/internal/promise.js b/lib/internal/promise.js index 76acf4806eeea6..a294471f1f8ab3 100644 --- a/lib/internal/promise.js +++ b/lib/internal/promise.js @@ -9,7 +9,7 @@ module.exports = { }; const originalThen = Promise.prototype.then; -Promise.prototype.then = function (success, failure) { +Promise.prototype.then = function(success, failure) { var promise; switch (arguments.length) { case 2: @@ -36,10 +36,10 @@ Promise.prototype.then = function (success, failure) { var domainWrapHook = _noopDomainWrap; -function addDomainHook (hook) { +function addDomainHook(hook) { domainWrapHook = hook; } -function _noopDomainWrap (func) { +function _noopDomainWrap(func) { return func; } From 010224a18fb27a05870033fecae1632e96e2f1ad Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Fri, 5 Feb 2016 15:42:30 -0800 Subject: [PATCH 26/33] http{,s}: getAsync only listens for error once --- lib/http.js | 2 +- lib/https.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/http.js b/lib/http.js index cab8c0dbfb0fdc..a79fdc0cbea2e8 100644 --- a/lib/http.js +++ b/lib/http.js @@ -61,7 +61,7 @@ exports.getAsync = function(options) { const request = exports.request(options, (res) => { resolveRequest(res); }); - request.on('error', rejectRequest); + request.once('error', rejectRequest); getResponse.then(() => { request.removeListener('error', rejectRequest); }); diff --git a/lib/https.js b/lib/https.js index a21b48b1ad358b..5b78a13080384f 100644 --- a/lib/https.js +++ b/lib/https.js @@ -212,7 +212,7 @@ exports.getAsync = function(options) { const request = exports.request(options, (res) => { resolveRequest(res); }); - request.on('error', rejectRequest); + request.once('error', rejectRequest); getResponse.then(() => { request.removeListener('error', rejectRequest); }); From 6ae09c50135b43f4e40aa4cb5104da39f1315d82 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 9 Feb 2016 20:47:38 -0800 Subject: [PATCH 27/33] http{s,}: nix {request,get}Async --- lib/http.js | 34 ---------------------------------- lib/https.js | 34 ---------------------------------- 2 files changed, 68 deletions(-) diff --git a/lib/http.js b/lib/http.js index a79fdc0cbea2e8..64788dd5c07f9f 100644 --- a/lib/http.js +++ b/lib/http.js @@ -31,46 +31,12 @@ exports.request = function(options, cb) { return new ClientRequest(options, cb); }; -exports.requestAsync = function(options) { - var resolveRequest = null; - const getResponse = new Promise((resolve) => { - resolveRequest = resolve; - }); - const request = exports.request(options, (res) => { - resolveRequest(res); - }); - return { - request, - response: getResponse - }; -}; - exports.get = function(options, cb) { var req = exports.request(options, cb); req.end(); return req; }; -exports.getAsync = function(options) { - var resolveRequest = null; - var rejectRequest = null; - const getResponse = new Promise((resolve, reject) => { - resolveRequest = resolve; - rejectRequest = reject; - }); - const request = exports.request(options, (res) => { - resolveRequest(res); - }); - request.once('error', rejectRequest); - getResponse.then(() => { - request.removeListener('error', rejectRequest); - }); - return { - request, - response: getResponse - }; -}; - exports._connectionListener = server._connectionListener; const Server = exports.Server = server.Server; diff --git a/lib/https.js b/lib/https.js index 5b78a13080384f..bcb0057ea5d63b 100644 --- a/lib/https.js +++ b/lib/https.js @@ -182,42 +182,8 @@ exports.request = function(options, cb) { return http.request(options, cb); }; -exports.requestAsync = function(options) { - var resolveRequest = null; - const getResponse = new Promise((resolve) => { - resolveRequest = resolve; - }); - const request = exports.request(options, (res) => { - resolveRequest(res); - }); - return { - request, - response: getResponse - }; -}; - exports.get = function(options, cb) { const req = exports.request(options, cb); req.end(); return req; }; - -exports.getAsync = function(options) { - var resolveRequest = null; - var rejectRequest = null; - const getResponse = new Promise((resolve, reject) => { - resolveRequest = resolve; - rejectRequest = reject; - }); - const request = exports.request(options, (res) => { - resolveRequest(res); - }); - request.once('error', rejectRequest); - getResponse.then(() => { - request.removeListener('error', rejectRequest); - }); - return { - request, - response: getResponse - }; -}; From 670a9c2a9f743bc77b421e44397362889f532f60 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 9 Feb 2016 21:02:56 -0800 Subject: [PATCH 28/33] rework promisifier --- lib/child_process.js | 4 +- lib/cluster.js | 2 +- lib/crypto.js | 10 ++- lib/dgram.js | 8 +- lib/dns.js | 26 +++--- lib/fs.js | 60 ++++++------- lib/internal/child_process.js | 2 +- lib/internal/promisify.js | 88 ++++++++++++------- lib/net.js | 4 +- lib/readline.js | 2 +- lib/repl.js | 2 +- lib/tls.js | 2 - lib/zlib.js | 14 +-- .../test-promise-domain-interaction.js | 22 ++--- 14 files changed, 134 insertions(+), 112 deletions(-) diff --git a/lib/child_process.js b/lib/child_process.js index c1398ca0f7e6f7..60ec80158b836d 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -103,7 +103,7 @@ exports.exec = function(command /*, options, callback*/) { }; -exports.execAsync = promisify(exports.exec); +promisify.multiple(exports, 'exec', ['stdout', 'stderr']); exports.execFile = function(file /*, args, options, callback*/) { @@ -293,7 +293,7 @@ exports.execFile = function(file /*, args, options, callback*/) { }; -exports.execFileAsync = promisify(exports.execFile); +promisify.multiple(exports, 'execFile', ['stdout', 'stderr']); var _deprecatedCustomFds = internalUtil.deprecate(function(options) { diff --git a/lib/cluster.js b/lib/cluster.js index 0f953861aaade4..1c62ab99fdf600 100644 --- a/lib/cluster.js +++ b/lib/cluster.js @@ -407,7 +407,7 @@ function masterInit() { } if (cb) intercom.once('disconnect', cb); }; - cluster.disconnectAsync = promisify(cluster.disconnect); + promisify.normal(cluster, 'disconnect'); Worker.prototype.disconnect = function() { this.suicide = true; diff --git a/lib/crypto.js b/lib/crypto.js index 9fdbc1b5aca8d0..228f45eaf36fd9 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -554,7 +554,7 @@ exports.pbkdf2 = function(password, return pbkdf2(password, salt, iterations, keylen, digest, callback); }; -exports.pbkdf2Async = promisify(exports.pbkdf2); +promisify.normal(exports, 'pbkdf2'); exports.pbkdf2Sync = function(password, salt, iterations, keylen, digest) { @@ -630,10 +630,12 @@ exports.setEngine = function setEngine(id, flags) { }; exports.randomBytes = exports.pseudoRandomBytes = randomBytes; -exports.randomBytesAsync = -exports.pseudoRandomBytesAsync = promisify(randomBytes); exports.rng = exports.prng = randomBytes; -exports.rngAsync = exports.prngAsync = promisify(randomBytes); + +promisify.normal(exports, 'randomBytes'); +promisify.normal(exports, 'pseudoRandomBytes'); +promisify.normal(exports, 'rng'); +promisify.normal(exports, 'prng'); exports.getCiphers = function() { return filterDuplicates(getCiphers()); diff --git a/lib/dgram.js b/lib/dgram.js index 42f8e068bd01da..c184471d21ec34 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -225,7 +225,7 @@ Socket.prototype.bind = function(port_ /*, address, callback*/) { return self; }; -Socket.prototype.bindAsync = promisify(Socket.prototype.bind); +promisify.normal(Socket.prototype, 'bind'); // thin wrapper around `send`, here for compatibility with dgram_legacy.js @@ -243,7 +243,7 @@ Socket.prototype.sendto = function(buffer, this.send(buffer, offset, length, port, address, callback); }; -Socket.prototype.sendtoAsync = promisify(Socket.prototype.sendto); +promisify.normal(Socket.prototype, 'sendto'); function sliceBuffer(buffer, offset, length) { @@ -343,7 +343,7 @@ Socket.prototype.send = function(buffer, doSend(ex, self, ip, buffer, address, port, callback); }); }; -Socket.prototype.sendAsync = promisify(Socket.prototype.send); +promisify.normal(Socket.prototype, 'send'); function doSend(ex, self, ip, buffer, address, port, callback) { if (ex) { @@ -399,7 +399,7 @@ Socket.prototype.close = function(callback) { return this; }; -Socket.prototype.closeAsync = promisify(Socket.prototype.close); +promisify.normal(Socket.prototype, 'close'); function socketCloseNT(self) { diff --git a/lib/dns.js b/lib/dns.js index df11897bd0dd59..24e866ed29fce7 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -172,7 +172,7 @@ exports.lookup = function lookup(hostname, options, callback) { callback.immediately = true; return req; }; -exports.lookupAsync = promisify(exports.lookup); +promisify.normal(exports, 'lookup'); function onlookupservice(err, host, service) { @@ -208,7 +208,7 @@ exports.lookupService = function(host, port, callback) { callback.immediately = true; return req; }; -exports.lookupServiceAsync = promisify(exports.lookupService); +promisify.normal(exports, 'lookupService'); function onresolve(err, result) { @@ -255,16 +255,16 @@ exports.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr'); exports.resolveSoa = resolveMap.SOA = resolver('querySoa'); exports.reverse = resolveMap.PTR = resolver('getHostByAddr'); -exports.resolve4Async = promisify(resolver('queryA')); -exports.resolve6Async = promisify(resolver('queryAaaa')); -exports.resolveCnameAsync = promisify(resolver('queryCname')); -exports.resolveMxAsync = promisify(resolver('queryMx')); -exports.resolveNsAsync = promisify(resolver('queryNs')); -exports.resolveTxtAsync = promisify(resolver('queryTxt')); -exports.resolveSrvAsync = promisify(resolver('querySrv')); -exports.resolveNaptrAsync = promisify(resolver('queryNaptr')); -exports.resolveSoaAsync = promisify(resolver('querySoa')); -exports.reverseAsync = promisify(resolver('getHostByAddr')); +promisify.normal(exports, 'resolve4'); +promisify.normal(exports, 'resolve6'); +promisify.normal(exports, 'resolveCname'); +promisify.normal(exports, 'resolveMx'); +promisify.normal(exports, 'resolveNs'); +promisify.normal(exports, 'resolveTxt'); +promisify.normal(exports, 'resolveSrv'); +promisify.normal(exports, 'resolveNaptr'); +promisify.normal(exports, 'resolveSoa'); +promisify.normal(exports, 'reverse'); exports.resolve = function(hostname, type_, callback_) { @@ -285,7 +285,7 @@ exports.resolve = function(hostname, type_, callback_) { throw new Error('Unknown type "' + type_ + '"'); } }; -exports.resolveAsync = promisify(exports.resolve); +promisify.normal(exports, 'resolve'); exports.getServers = function() { diff --git a/lib/fs.js b/lib/fs.js index 9e31c49c725a8c..88403ac1d5611f 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -196,7 +196,7 @@ fs.access = function(path, mode, callback) { req.oncomplete = makeCallback(callback); binding.access(pathModule._makeLong(path), mode, req); }; -fs.accessAsync = promisify(fs.access); +promisify.normal(fs, 'access'); fs.accessSync = function(path, mode) { nullCheck(path); @@ -218,7 +218,7 @@ fs.exists = function(path, callback) { if (callback) callback(err ? false : true); } }; -fs.existsAsync = promisify(fs.exists, 1); +promisify.single(fs, 'exists'); fs.existsSync = function(path) { try { @@ -267,7 +267,7 @@ fs.readFile = function(path, options, callback_) { 0o666, req); }; -fs.readFileAsync = promisify(fs.readFile); +promisify.normal(fs, 'readFile'); const kReadFileBufferLength = 8 * 1024; @@ -552,7 +552,7 @@ fs.close = function(fd, callback) { req.oncomplete = makeCallback(callback); binding.close(fd, req); }; -fs.closeAsync = promisify(fs.close); +promisify.normal(fs, 'close'); fs.closeSync = function(fd) { return binding.close(fd); @@ -582,7 +582,7 @@ fs.open = function(path, flags, mode, callback_) { mode, req); }; -fs.openAsync = promisify(fs.open); +promisify.normal(fs, 'open'); fs.openSync = function(path, flags, mode) { mode = modeNum(mode, 0o666); @@ -742,7 +742,7 @@ fs.rename = function(oldPath, newPath, callback) { pathModule._makeLong(newPath), req); }; -fs.renameAsync = promisify(fs.rename); +promisify.normal(fs, 'rename'); fs.renameSync = function(oldPath, newPath) { nullCheck(oldPath); @@ -806,7 +806,7 @@ fs.ftruncate = function(fd, len, callback) { req.oncomplete = makeCallback(callback); binding.ftruncate(fd, len, req); }; -fs.ftruncateAsync = promisify(fs.ftruncate); +promisify.normal(fs, 'ftruncate'); fs.ftruncateSync = function(fd, len) { if (len === undefined) { @@ -822,7 +822,7 @@ fs.rmdir = function(path, callback) { req.oncomplete = callback; binding.rmdir(pathModule._makeLong(path), req); }; -fs.rmdirAsync = promisify(fs.rmdir); +promisify.normal(fs, 'rmdir'); fs.rmdirSync = function(path) { nullCheck(path); @@ -834,7 +834,7 @@ fs.fdatasync = function(fd, callback) { req.oncomplete = makeCallback(callback); binding.fdatasync(fd, req); }; -fs.fdatasyncAsync = promisify(fs.fdatasync); +promisify.normal(fs, 'fdatasync'); fs.fdatasyncSync = function(fd) { return binding.fdatasync(fd); @@ -845,7 +845,7 @@ fs.fsync = function(fd, callback) { req.oncomplete = makeCallback(callback); binding.fsync(fd, req); }; -fs.fsyncAsync = promisify(fs.fsync); +promisify.normal(fs, 'fsync'); fs.fsyncSync = function(fd) { return binding.fsync(fd); @@ -861,7 +861,7 @@ fs.mkdir = function(path, mode, callback) { modeNum(mode, 0o777), req); }; -fs.mkdirAsync = promisify(fs.mkdir); +promisify.normal(fs, 'mkdir'); fs.mkdirSync = function(path, mode) { nullCheck(path); @@ -876,7 +876,7 @@ fs.readdir = function(path, callback) { req.oncomplete = callback; binding.readdir(pathModule._makeLong(path), req); }; -fs.readdirAsync = promisify(fs.readdir); +promisify.normal(fs, 'readdir'); fs.readdirSync = function(path) { nullCheck(path); @@ -888,7 +888,7 @@ fs.fstat = function(fd, callback) { req.oncomplete = makeCallback(callback); binding.fstat(fd, req); }; -fs.fstatAsync = promisify(fs.fstat); +promisify.normal(fs, 'fstat'); fs.lstat = function(path, callback) { callback = makeCallback(callback); @@ -897,7 +897,7 @@ fs.lstat = function(path, callback) { req.oncomplete = callback; binding.lstat(pathModule._makeLong(path), req); }; -fs.lstatAsync = promisify(fs.lstat); +promisify.normal(fs, 'lstat'); fs.stat = function(path, callback) { callback = makeCallback(callback); @@ -906,7 +906,7 @@ fs.stat = function(path, callback) { req.oncomplete = callback; binding.stat(pathModule._makeLong(path), req); }; -fs.statAsync = promisify(fs.stat); +promisify.normal(fs, 'stat'); fs.fstatSync = function(fd) { return binding.fstat(fd); @@ -929,7 +929,7 @@ fs.readlink = function(path, callback) { req.oncomplete = callback; binding.readlink(pathModule._makeLong(path), req); }; -fs.readlinkAsync = promisify(fs.readlink); +promisify.normal(fs, 'readlink'); fs.readlinkSync = function(path) { nullCheck(path); @@ -966,7 +966,7 @@ fs.symlink = function(target, path, type_, callback_) { type, req); }; -fs.symlinkAsync = promisify(fs.symlink); +promisify.normal(fs, 'symlink'); fs.symlinkSync = function(target, path, type) { type = (typeof type === 'string' ? type : null); @@ -991,7 +991,7 @@ fs.link = function(srcpath, dstpath, callback) { pathModule._makeLong(dstpath), req); }; -fs.linkAsync = promisify(fs.link); +promisify.normal(fs, 'link'); fs.linkSync = function(srcpath, dstpath) { nullCheck(srcpath); @@ -1007,7 +1007,7 @@ fs.unlink = function(path, callback) { req.oncomplete = callback; binding.unlink(pathModule._makeLong(path), req); }; -fs.unlinkAsync = promisify(fs.unlink); +promisify.normal(fs, 'unlink'); fs.unlinkSync = function(path) { nullCheck(path); @@ -1019,7 +1019,7 @@ fs.fchmod = function(fd, mode, callback) { req.oncomplete = makeCallback(callback); binding.fchmod(fd, modeNum(mode), req); }; -fs.fchmodAsync = promisify(fs.fchmod); +promisify.normal(fs, 'fchmod'); fs.fchmodSync = function(fd, mode) { return binding.fchmod(fd, modeNum(mode)); @@ -1042,7 +1042,7 @@ if (constants.hasOwnProperty('O_SYMLINK')) { }); }); }; - fs.lchmodAsync = promisify(fs.lchmod); + promisify.normal(fs, 'lchmod'); fs.lchmodSync = function(path, mode) { var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); @@ -1075,7 +1075,7 @@ fs.chmod = function(path, mode, callback) { modeNum(mode), req); }; -fs.chmodAsync = promisify(fs.chmod); +promisify.normal(fs, 'chmod'); fs.chmodSync = function(path, mode) { nullCheck(path); @@ -1093,7 +1093,7 @@ if (constants.hasOwnProperty('O_SYMLINK')) { fs.fchown(fd, uid, gid, callback); }); }; - fs.lchownAsync = promisify(fs.lchown); + promisify.normal(fs, 'lchown'); fs.lchownSync = function(path, uid, gid) { var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK); @@ -1106,7 +1106,7 @@ fs.fchown = function(fd, uid, gid, callback) { req.oncomplete = makeCallback(callback); binding.fchown(fd, uid, gid, req); }; -fs.fchownAsync = promisify(fs.fchown); +promisify.normal(fs, 'fchown'); fs.fchownSync = function(fd, uid, gid) { return binding.fchown(fd, uid, gid); @@ -1119,7 +1119,7 @@ fs.chown = function(path, uid, gid, callback) { req.oncomplete = callback; binding.chown(pathModule._makeLong(path), uid, gid, req); }; -fs.chownAsync = promisify(fs.chown); +promisify.normal(fs, 'chown'); fs.chownSync = function(path, uid, gid) { nullCheck(path); @@ -1157,7 +1157,7 @@ fs.utimes = function(path, atime, mtime, callback) { toUnixTimestamp(mtime), req); }; -fs.utimesAsync = promisify(fs.utimes); +promisify.normal(fs, 'utimes'); fs.utimesSync = function(path, atime, mtime) { nullCheck(path); @@ -1173,7 +1173,7 @@ fs.futimes = function(fd, atime, mtime, callback) { req.oncomplete = makeCallback(callback); binding.futimes(fd, atime, mtime, req); }; -fs.futimesAsync = promisify(fs.futimes); +promisify.normal(fs, 'futimes'); fs.futimesSync = function(fd, atime, mtime) { atime = toUnixTimestamp(atime); @@ -1249,7 +1249,7 @@ fs.writeFile = function(path, data, options, callback_) { writeAll(fd, isUserFd, buffer, 0, buffer.length, position, callback); } }; -fs.writeFileAsync = promisify(fs.writeFile); +promisify.normal(fs, 'writeFile'); fs.writeFileSync = function(path, data, options) { if (!options) { @@ -1306,7 +1306,7 @@ fs.appendFile = function(path, data, options, callback_) { fs.writeFile(path, data, options, callback); }; -fs.appendFileAsync = promisify(fs.appendFile); +promisify.normal(fs, 'appendFile'); fs.appendFileSync = function(path, data, options) { if (!options) { @@ -1721,7 +1721,7 @@ fs.realpath = function realpath(p, cache, cb) { start(); } }; -fs.realpathAsync = promisify(fs.realpath); +promisify.normal(fs, 'realpath'); var pool; diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index 101b1533def34a..2a7a19fc4bab5b 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -519,7 +519,7 @@ function setupChannel(target, channel) { } return false; }; - target.sendAsync = promisify(target.send); + promisify.normal(target, 'send'); target._send = function(message, handle, swallowErrors, callback) { assert(this.connected || this._channel); diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index 8ee8c97212af0b..792ca30cb26226 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -1,43 +1,67 @@ 'use strict'; -module.exports = promisify; +const SUFFIX = '$' -function promisify(fn, single) { - Object.defineProperty(inner, 'name', { - value: fn.name + 'Promisified' - }); - return inner; +module.exports = { + normal, + single, + multiple +}; - function inner() { - if (typeof arguments[arguments.length - 1] === 'function') { - return fn.apply(this, arguments); +function normal(object, name) { + return _promisify(object, name, (err, val) => { + if (err) { + throw err; } - var resolve = null; - var reject = null; - const args = Array.from(arguments); - const promise = new Promise((_resolve, _reject) => { - resolve = _resolve; - reject = _reject; - }); + return val; + }); +} - try { - args.push(single ? resolve : resolver); - fn.apply(this, args); - } catch (err) { - reject(err); +function single(object, name) { + return _promisify(object, name, (val) => { + return val; + }); +} + +function multiple(object, name, mapped) { + return _promisify(object, name, (err) => { + if (err) { + throw err; } - return promise; + const output = {}; + for (var i = 0; i < mapped.length; ++i) { + output[mapped[i]] = arguments[i + 1]; + } + return output; + }); +} - function resolver(err) { - if (err) { - return reject(err); - } - switch (arguments.length) { - case 0: return resolve(); - case 1: return resolve(); - case 2: return resolve(arguments[1]); +function _promisify(object, name, handler) { + _install(object, name, inner); + + function inner() { + return new Promise((resolve, reject) => { + // this might be slower than array manipulation, but it might not be. + fn.call(this, ...arguments, single ? resolve : resolver); + + function resolver() { + try { + resolve(handler(Array.from(arguments))); + } catch (err) { + reject(err); + } } - return resolve(Array.from(arguments).slice(1)); - } + }); } } + +function _install(object, name, inner) { + const fn = object[name]; + Object.defineProperty(inner, 'name', { + value: (fn.name || name) + 'Promisified' + }); + Object.defineProperty(object, `${name}${SUFFIX}`, { + enumerable: false, + value: inner + }); +} diff --git a/lib/net.js b/lib/net.js index ac78b03d520736..39f15227d3b5a6 100644 --- a/lib/net.js +++ b/lib/net.js @@ -66,8 +66,6 @@ exports.connect = exports.createConnection = function() { return Socket.prototype.connect.apply(s, args); }; -exports.connectAsync = promisify(exports.connect); - // Returns an array [options] or [options, cb] // It is the same as the argument of Socket.prototype.connect(). function normalizeConnectArgs(args) { @@ -320,7 +318,7 @@ Socket.prototype.setTimeout = function(msecs, callback) { } return this; }; -Socket.prototype.setTimeoutAsync = promisify(Socket.prototype.setTimeout); +promisify.normal(Socket.prototype, 'setTimeout'); Socket.prototype._onTimeout = function() { diff --git a/lib/readline.js b/lib/readline.js index 9413e557726092..b4f2c561b746a9 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -206,7 +206,7 @@ Interface.prototype.question = function(query, cb) { } } }; -Interface.prototype.questionAsync = promisify(Interface.prototype.question); +promisify.normal(Interface.prototype, 'question'); Interface.prototype._onLine = function(line) { diff --git a/lib/repl.js b/lib/repl.js index 24821050ad97dd..e78ef9174ba810 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -886,7 +886,7 @@ REPLServer.prototype.complete = function(line, callback) { callback(null, [completions || [], completeOn]); } }; -REPLServer.prototype.completeAsync = promisify(REPLServer.prototype.complete); +promisify.normal(REPLServer.prototype, 'complete'); /** diff --git a/lib/tls.js b/lib/tls.js index 26e4f2d376a074..245afb6bdc7561 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -5,7 +5,6 @@ const url = require('url'); const binding = process.binding('crypto'); const Buffer = require('buffer').Buffer; const constants = require('constants'); -const promisify = require('internal/promisify'); // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations // every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more @@ -238,5 +237,4 @@ exports.TLSSocket = require('_tls_wrap').TLSSocket; exports.Server = require('_tls_wrap').Server; exports.createServer = require('_tls_wrap').createServer; exports.connect = require('_tls_wrap').connect; -exports.connectAsync = promisify(require('_tls_wrap').connect); exports.createSecurePair = require('_tls_legacy').createSecurePair; diff --git a/lib/zlib.js b/lib/zlib.js index 37faea0d1d305d..64674d65bff1cf 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -111,7 +111,7 @@ exports.deflate = function(buffer, opts, callback) { } return zlibBuffer(new Deflate(opts), buffer, callback); }; -exports.deflateAsync = promisify(exports.deflate); +promisify.normal(exports, 'deflate'); exports.deflateSync = function(buffer, opts) { return zlibBufferSync(new Deflate(opts), buffer); @@ -124,7 +124,7 @@ exports.gzip = function(buffer, opts, callback) { } return zlibBuffer(new Gzip(opts), buffer, callback); }; -exports.gzipAsync = promisify(exports.gzip); +promisify.normal(exports, 'gzip'); exports.gzipSync = function(buffer, opts) { return zlibBufferSync(new Gzip(opts), buffer); @@ -137,7 +137,7 @@ exports.deflateRaw = function(buffer, opts, callback) { } return zlibBuffer(new DeflateRaw(opts), buffer, callback); }; -exports.deflateRawAsync = promisify(exports.deflateRaw); +promisify.normal(exports, 'deflateRaw'); exports.deflateRawSync = function(buffer, opts) { return zlibBufferSync(new DeflateRaw(opts), buffer); @@ -150,7 +150,7 @@ exports.unzip = function(buffer, opts, callback) { } return zlibBuffer(new Unzip(opts), buffer, callback); }; -exports.unzipAsync = promisify(exports.unzip); +promisify.normal(exports, 'unzip'); exports.unzipSync = function(buffer, opts) { return zlibBufferSync(new Unzip(opts), buffer); @@ -163,7 +163,7 @@ exports.inflate = function(buffer, opts, callback) { } return zlibBuffer(new Inflate(opts), buffer, callback); }; -exports.inflateAsync = promisify(exports.inflate); +promisify.normal(exports, 'inflate'); exports.inflateSync = function(buffer, opts) { return zlibBufferSync(new Inflate(opts), buffer); @@ -176,7 +176,7 @@ exports.gunzip = function(buffer, opts, callback) { } return zlibBuffer(new Gunzip(opts), buffer, callback); }; -exports.gunzipAsync = promisify(exports.gunzip); +promisify.normal(exports, 'gunzip'); exports.gunzipSync = function(buffer, opts) { return zlibBufferSync(new Gunzip(opts), buffer); @@ -189,7 +189,7 @@ exports.inflateRaw = function(buffer, opts, callback) { } return zlibBuffer(new InflateRaw(opts), buffer, callback); }; -exports.inflateRawAsync = promisify(exports.inflateRaw); +promisify.normal(exports, 'inflateRaw'); exports.inflateRawSync = function(buffer, opts) { return zlibBufferSync(new InflateRaw(opts), buffer); diff --git a/test/parallel/test-promise-domain-interaction.js b/test/parallel/test-promise-domain-interaction.js index c75148077635dd..83c050a9cdd6c0 100644 --- a/test/parallel/test-promise-domain-interaction.js +++ b/test/parallel/test-promise-domain-interaction.js @@ -1,10 +1,10 @@ -'use strict' +'use strict'; -const common = require('../common'); +require('../common'); const domain = require('domain'); const assert = require('assert'); const fs = require('fs'); -const d = domain.create() +const d = domain.create(); const acc = []; @@ -12,20 +12,20 @@ d.on('error', function(err) { acc.push(err.message); }); d.run(function() { - const p = fs.openAsync(__filename, 'r') - + const p = fs.openAsync(__filename, 'r'); + p.then(function(fd) { - fs.readFileAsync(fd, function() { + fs.readFile$(fd, function() { throw new Error('one'); }); return fd; }, function() { - setTimeout(function () { - throw new Error('should not reach this point.') + setTimeout(function() { + throw new Error('should not reach this point.'); }); }).then(function(fd) { setTimeout(function() { - throw new Error('two') + throw new Error('two'); }); return fd; }).then(function(fd) { @@ -33,8 +33,8 @@ d.run(function() { }); }); -process.on('exit', function () { +process.on('exit', function() { assert.ok(acc.indexOf('one') !== -1); assert.ok(acc.indexOf('two') !== -1); assert.equal(acc.length, 2); -}) +}); From 240c72d0aa2c772eac602fdc10a97fedafb00fbe Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Tue, 9 Feb 2016 21:44:58 -0800 Subject: [PATCH 29/33] src: add flag --- lib/internal/promisify.js | 7 ++++++- src/node.cc | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index 792ca30cb26226..121f9efb32c2ad 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -2,6 +2,8 @@ const SUFFIX = '$' +const install = process._enablePromises ? _install : _noop + module.exports = { normal, single, @@ -37,7 +39,7 @@ function multiple(object, name, mapped) { } function _promisify(object, name, handler) { - _install(object, name, inner); + install(object, name, inner); function inner() { return new Promise((resolve, reject) => { @@ -65,3 +67,6 @@ function _install(object, name, inner) { value: inner }); } + +function _noop(object, name, inner) { +} diff --git a/src/node.cc b/src/node.cc index 29127fbfc620ca..e2caedea1bf40a 100644 --- a/src/node.cc +++ b/src/node.cc @@ -131,6 +131,7 @@ using v8::Uint32Array; using v8::V8; using v8::Value; +static bool enable_promises = false; static bool print_eval = false; static bool force_repl = false; static bool syntax_check_only = false; @@ -2988,6 +2989,11 @@ void SetupProcessObject(Environment* env, READONLY_PROPERTY(process, "_forceRepl", True(env->isolate())); } + // --enable-promises + if (enable_promises) { + READONLY_PROPERTY(process, "_enablePromises", True(env->isolate())); + } + if (preload_module_count) { CHECK(preload_modules); Local array = Array::New(env->isolate()); @@ -3271,6 +3277,8 @@ static void PrintHelp() { " present.\n" #endif #endif + " --enable-promises EXPERIMENTAL: enable promisified API\n" + " (see nodejs/node#5020)\n" "\n" "Environment variables:\n" #ifdef _WIN32 @@ -3378,6 +3386,8 @@ static void ParseArgs(int* argc, } args_consumed += 1; local_preload_modules[preload_module_count++] = module; + } else if (strcmp(arg, "--enable-promises") == 0) { + enable_promises = true; } else if (strcmp(arg, "--check") == 0 || strcmp(arg, "-c") == 0) { syntax_check_only = true; } else if (strcmp(arg, "--interactive") == 0 || strcmp(arg, "-i") == 0) { From 565dfe2720184413f94e74a3583f802142b7f31d Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Wed, 10 Feb 2016 20:30:27 -0800 Subject: [PATCH 30/33] fix handler --- lib/internal/promisify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index 121f9efb32c2ad..525ad465116914 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -48,7 +48,7 @@ function _promisify(object, name, handler) { function resolver() { try { - resolve(handler(Array.from(arguments))); + resolve(handler(...arguments)); } catch (err) { reject(err); } From 3384ab05dbcabe3a8c9281481838409d5de58225 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Wed, 10 Feb 2016 20:43:53 -0800 Subject: [PATCH 31/33] introduce .promised --- lib/internal/promisify.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index 525ad465116914..f0f3ed53f16dfa 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -1,8 +1,8 @@ 'use strict'; -const SUFFIX = '$' - -const install = process._enablePromises ? _install : _noop +const SUFFIX = 'Promise'; +const SHORTCUT_NAME = 'promised'; +const install = process._enablePromises ? _install : _noop; module.exports = { normal, @@ -26,29 +26,27 @@ function single(object, name) { } function multiple(object, name, mapped) { - return _promisify(object, name, (err) => { + return _promisify(object, name, (err, arg0, arg1) => { if (err) { throw err; } - const output = {}; - for (var i = 0; i < mapped.length; ++i) { - output[mapped[i]] = arguments[i + 1]; - } - return output; + return { + [mapped[0]]: arg0, + [mapped[1]]: arg1 + }; }); } function _promisify(object, name, handler) { - install(object, name, inner); + const fn = install(object, name, inner); function inner() { return new Promise((resolve, reject) => { - // this might be slower than array manipulation, but it might not be. - fn.call(this, ...arguments, single ? resolve : resolver); + fn.call(this, ...arguments, resolver); - function resolver() { + function resolver(arg0, arg1, arg2) { try { - resolve(handler(...arguments)); + resolve(handler(arg0, arg1, arg2)); } catch (err) { reject(err); } @@ -66,6 +64,12 @@ function _install(object, name, inner) { enumerable: false, value: inner }); + + if (!object[SHORTCUT_NAME]) { + object[SHORTCUT_NAME] = {}; + } + object[SHORTCUT_NAME][name] = inner; + return fn; } function _noop(object, name, inner) { From 4e38057a76b0ddc11ad5faabf6bacb295ad354a2 Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Wed, 10 Feb 2016 21:47:37 -0800 Subject: [PATCH 32/33] bugfix: use outer arguments --- lib/internal/promisify.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index f0f3ed53f16dfa..a70447e61f81cb 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -41,8 +41,9 @@ function _promisify(object, name, handler) { const fn = install(object, name, inner); function inner() { + const args = Array.from(arguments); return new Promise((resolve, reject) => { - fn.call(this, ...arguments, resolver); + fn.call(this, ...args, resolver); function resolver(arg0, arg1, arg2) { try { From 8c975499a951779c115dc2998d8df1b2976d22fa Mon Sep 17 00:00:00 2001 From: Chris Dickinson Date: Wed, 10 Feb 2016 21:54:44 -0800 Subject: [PATCH 33/33] wip: add recovery object --- lib/internal/promisify.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/internal/promisify.js b/lib/internal/promisify.js index a70447e61f81cb..6dfad224a236c4 100644 --- a/lib/internal/promisify.js +++ b/lib/internal/promisify.js @@ -11,8 +11,14 @@ module.exports = { }; function normal(object, name) { - return _promisify(object, name, (err, val) => { + return _promisify(object, name, (lastArg, err, val) => { if (err) { + if (lastArg && + typeof lastArg === 'object' && + err.code && + typeof lastArg[err.code] === 'function') { + return lastArg[err.code]() + } throw err; } return val; @@ -20,14 +26,20 @@ function normal(object, name) { } function single(object, name) { - return _promisify(object, name, (val) => { + return _promisify(object, name, (lastArg, val) => { return val; }); } function multiple(object, name, mapped) { - return _promisify(object, name, (err, arg0, arg1) => { + return _promisify(object, name, (lastArg, err, arg0, arg1) => { if (err) { + if (lastArg && + typeof lastArg === 'object' && + err.code && + typeof lastArg[err.code] === 'function') { + return lastArg[err.code]() + } throw err; } return { @@ -42,12 +54,13 @@ function _promisify(object, name, handler) { function inner() { const args = Array.from(arguments); + const lastArg = args[args.length - 1]; return new Promise((resolve, reject) => { fn.call(this, ...args, resolver); function resolver(arg0, arg1, arg2) { try { - resolve(handler(arg0, arg1, arg2)); + resolve(handler(lastArg, arg0, arg1, arg2)); } catch (err) { reject(err); }