diff --git a/doc/api/child_process.md b/doc/api/child_process.md index b3e3001417cc4e..39ac6c62a603fa 100644 --- a/doc/api/child_process.md +++ b/doc/api/child_process.md @@ -214,6 +214,23 @@ child runs longer than `timeout` milliseconds. *Note: Unlike the exec(3) POSIX system call, `child_process.exec()` does not replace the existing process and uses a shell to execute the command.* +If this method is invoked as its [`util.promisify()`][]ed version, it returns +a Promise for an object with `stdout` and `stderr` properties. + +For example: + +```js +const util = require('util'); +const exec = util.promisify(require('child_process').exec); + +async function lsExample() { + const {stdout, stderr} = await exec('ls'); + console.log('stdout:', stdout); + console.log('stderr:', stderr); +} +lsExample(); +``` + ### child_process.execFile(file[, args][, options][, callback]) + +* `original` {Function} + +Takes a function following the common Node.js callback style, i.e. taking a +`(err, value) => ...` callback as the last argument, and returns a version +that returns promises. + +For example: + +```js +const util = require('util'); +const fs = require('fs'); + +const stat = util.promisify(fs.stat); +stat('.').then((stats) => { + // Do something with `stats` +}).catch((error) => { + // Handle the error. +}); +``` + +Or, equivalently using `async function`s: + +```js +const util = require('util'); +const fs = require('fs'); + +const stat = util.promisify(fs.stat); + +async function callStat() { + const stats = await stat('.'); + console.log(`This directory is owned by ${stats.uid}`); +} +``` + +If there is an `original[util.promisify.custom]` property present, `promisify` +will return its value, see [Custom promisified functions][]. + +`promisify()` assumes that `original` is a function taking a callback as its +final argument in all cases, and the returned function will result in undefined +behaviour if it does not. + +### Custom promisified functions + +Using the `util.promisify.custom` symbol one can override the return value of +[`util.promisify()`][]: + +```js +const util = require('util'); + +function doSomething(foo, callback) { + // ... +} + +doSomething[util.promisify.custom] = function(foo) { + return getPromiseSomehow(); +}; + +const promisified = util.promisify(doSomething); +console.log(promisified === doSomething[util.promisify.custom]); + // prints 'true' +``` + +This can be useful for cases where the original function does not follow the +standard format of taking an error-first callback as the last argument. + +### util.promisify.custom + + +* {symbol} + +A Symbol that can be used to declare custom promisified variants of functions, +see [Custom promisified functions][]. + ## Deprecated APIs The following APIs have been deprecated and should no longer be used. Existing @@ -878,7 +958,9 @@ Deprecated predecessor of `console.log`. [`console.error()`]: console.html#console_console_error_data_args [`console.log()`]: console.html#console_console_log_data_args [`util.inspect()`]: #util_util_inspect_object_options +[`util.promisify()`]: #util_util_promisify_original [Custom inspection functions on Objects]: #util_custom_inspection_functions_on_objects [Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors +[Custom promisified functions]: #util_custom_promisified_functions [constructor]: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor [semantically incompatible]: https://github.com/nodejs/node/issues/4179 diff --git a/lib/child_process.js b/lib/child_process.js index 34e420eadab72a..95b643c85d131e 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -22,7 +22,9 @@ 'use strict'; const util = require('util'); -const { deprecate, convertToValidSignal } = require('internal/util'); +const { + deprecate, convertToValidSignal, customPromisifyArgs +} = require('internal/util'); const debug = util.debuglog('child_process'); const uv = process.binding('uv'); @@ -138,6 +140,9 @@ exports.exec = function(command /*, options, callback*/) { opts.callback); }; +Object.defineProperty(exports.exec, customPromisifyArgs, + { value: ['stdout', 'stderr'], enumerable: false }); + exports.execFile = function(file /*, args, options, callback*/) { var args = []; @@ -333,6 +338,9 @@ exports.execFile = function(file /*, args, options, callback*/) { return child; }; +Object.defineProperty(exports.execFile, customPromisifyArgs, + { value: ['stdout', 'stderr'], enumerable: false }); + const _deprecatedCustomFds = deprecate( function deprecateCustomFds(options) { options.stdio = options.customFds.map(function mapCustomFds(fd) { diff --git a/lib/dns.js b/lib/dns.js index 95d90f6746af8a..49cd9c929415b8 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -26,6 +26,7 @@ const util = require('util'); const cares = process.binding('cares_wrap'); const uv = process.binding('uv'); const internalNet = require('internal/net'); +const { customPromisifyArgs } = require('internal/util'); const GetAddrInfoReqWrap = cares.GetAddrInfoReqWrap; const GetNameInfoReqWrap = cares.GetNameInfoReqWrap; @@ -189,6 +190,9 @@ function lookup(hostname, options, callback) { return req; } +Object.defineProperty(lookup, customPromisifyArgs, + { value: ['address', 'family'], enumerable: false }); + function onlookupservice(err, host, service) { if (err) @@ -228,6 +232,9 @@ function lookupService(host, port, callback) { return req; } +Object.defineProperty(lookupService, customPromisifyArgs, + { value: ['hostname', 'service'], enumerable: false }); + function onresolve(err, result, ttls) { if (ttls && this.ttl) diff --git a/lib/fs.js b/lib/fs.js index ac1f90109a9eda..b62bc67920bab7 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -656,6 +656,9 @@ fs.read = function(fd, buffer, offset, length, position, callback) { binding.read(fd, buffer, offset, length, position, req); }; +Object.defineProperty(fs.read, internalUtil.customPromisifyArgs, + { value: ['bytesRead', 'buffer'], enumerable: false }); + fs.readSync = function(fd, buffer, offset, length, position) { if (length === 0) { return 0; @@ -706,6 +709,9 @@ fs.write = function(fd, buffer, offset, length, position, callback) { return binding.writeString(fd, buffer, offset, length, req); }; +Object.defineProperty(fs.write, internalUtil.customPromisifyArgs, + { value: ['bytesWritten', 'buffer'], enumerable: false }); + // usage: // fs.writeSync(fd, buffer[, offset[, length[, position]]]); // OR diff --git a/lib/internal/util.js b/lib/internal/util.js index 1633a920d55209..4dfcf81837b912 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -4,6 +4,8 @@ const errors = require('internal/errors'); const binding = process.binding('util'); const signals = process.binding('constants').os.signals; +const { createPromise, promiseResolve, promiseReject } = binding; + const kArrowMessagePrivateSymbolIndex = binding['arrow_message_private_symbol']; const kDecoratedPrivateSymbolIndex = binding['decorated_private_symbol']; const noCrypto = !process.versions.openssl; @@ -217,3 +219,62 @@ module.exports = exports = { // default isEncoding implementation, just in case userland overrides it. kIsEncodingSymbol: Symbol('node.isEncoding') }; + +const kCustomPromisifiedSymbol = Symbol('util.promisify.custom'); +const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs'); + +function promisify(orig) { + if (typeof orig !== 'function') { + const errors = require('internal/errors'); + throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'original', 'function'); + } + + if (orig[kCustomPromisifiedSymbol]) { + const fn = orig[kCustomPromisifiedSymbol]; + if (typeof fn !== 'function') { + throw new TypeError('The [util.promisify.custom] property must be ' + + 'a function'); + } + Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, enumerable: false, writable: false, configurable: true + }); + return fn; + } + + // Names to create an object from in case the callback receives multiple + // arguments, e.g. ['stdout', 'stderr'] for child_process.exec. + const argumentNames = orig[kCustomPromisifyArgsSymbol]; + + function fn(...args) { + const promise = createPromise(); + try { + orig.call(this, ...args, (err, ...values) => { + if (err) { + promiseReject(promise, err); + } else if (argumentNames !== undefined && values.length > 1) { + const obj = {}; + for (var i = 0; i < argumentNames.length; i++) + obj[argumentNames[i]] = values[i]; + promiseResolve(promise, obj); + } else { + promiseResolve(promise, values[0]); + } + }); + } catch (err) { + promiseReject(promise, err); + } + return promise; + } + + Object.setPrototypeOf(fn, Object.getPrototypeOf(orig)); + + Object.defineProperty(fn, kCustomPromisifiedSymbol, { + value: fn, enumerable: false, writable: false, configurable: true + }); + return Object.defineProperties(fn, Object.getOwnPropertyDescriptors(orig)); +} + +promisify.custom = kCustomPromisifiedSymbol; + +exports.promisify = promisify; +exports.customPromisifyArgs = kCustomPromisifyArgsSymbol; diff --git a/lib/timers.js b/lib/timers.js index 115c3c82963530..5d21227b7b93df 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -23,6 +23,8 @@ const TimerWrap = process.binding('timer_wrap').Timer; const L = require('internal/linkedlist'); +const internalUtil = require('internal/util'); +const { createPromise, promiseResolve } = process.binding('util'); const assert = require('assert'); const util = require('util'); const debug = util.debuglog('timer'); @@ -364,7 +366,7 @@ exports.enroll = function(item, msecs) { */ -exports.setTimeout = function(callback, after, arg1, arg2, arg3) { +function setTimeout(callback, after, arg1, arg2, arg3) { if (typeof callback !== 'function') { throw new TypeError('"callback" argument must be a function'); } @@ -383,8 +385,16 @@ exports.setTimeout = function(callback, after, arg1, arg2, arg3) { } return createSingleTimeout(callback, after, args); +} + +setTimeout[internalUtil.promisify.custom] = function(after, value) { + const promise = createPromise(); + createSingleTimeout(promise, after, [value]); + return promise; }; +exports.setTimeout = setTimeout; + function createSingleTimeout(callback, after, args) { after *= 1; // coalesce to number or NaN if (!(after >= 1 && after <= TIMEOUT_MAX)) @@ -403,6 +413,8 @@ function createSingleTimeout(callback, after, args) { function ontimeout(timer) { var args = timer._timerArgs; var callback = timer._onTimeout; + if (typeof callback !== 'function') + return promiseResolve(callback, args[0]); if (!args) callback.call(timer); else { @@ -687,6 +699,8 @@ function tryOnImmediate(immediate, oldTail) { function runCallback(timer) { const argv = timer._argv; const argc = argv ? argv.length : 0; + if (typeof timer._callback !== 'function') + return promiseResolve(timer._callback, argv[0]); switch (argc) { // fast-path callbacks with 0-3 arguments case 0: @@ -715,7 +729,7 @@ function Immediate() { this.domain = process.domain; } -exports.setImmediate = function(callback, arg1, arg2, arg3) { +function setImmediate(callback, arg1, arg2, arg3) { if (typeof callback !== 'function') { throw new TypeError('"callback" argument must be a function'); } @@ -740,8 +754,16 @@ exports.setImmediate = function(callback, arg1, arg2, arg3) { break; } return createImmediate(args, callback); +} + +setImmediate[internalUtil.promisify.custom] = function(value) { + const promise = createPromise(); + createImmediate([value], promise); + return promise; }; +exports.setImmediate = setImmediate; + function createImmediate(args, callback) { // declaring it `const immediate` causes v6.0.0 to deoptimize this function var immediate = new Immediate(); diff --git a/lib/util.js b/lib/util.js index 4e626a3accc3f8..d73b12dcfd59b3 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1057,3 +1057,5 @@ exports._exceptionWithHostPort = function(err, // process.versions needs a custom function as some values are lazy-evaluated. process.versions[exports.inspect.custom] = (depth) => exports.format(JSON.parse(JSON.stringify(process.versions))); + +exports.promisify = internalUtil.promisify; diff --git a/src/node_util.cc b/src/node_util.cc index 5f3de643d4c2f3..50de94bfb2bf3a 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -12,6 +12,7 @@ using v8::Context; using v8::FunctionCallbackInfo; using v8::Integer; using v8::Local; +using v8::Maybe; using v8::Object; using v8::Private; using v8::Promise; @@ -20,6 +21,7 @@ using v8::Value; #define VALUE_METHOD_MAP(V) \ + V(isAsyncFunction, IsAsyncFunction) \ V(isDataView, IsDataView) \ V(isDate, IsDate) \ V(isExternal, IsExternal) \ @@ -147,6 +149,36 @@ void WatchdogHasPendingSigint(const FunctionCallbackInfo& args) { } +void CreatePromise(const FunctionCallbackInfo& args) { + Local context = args.GetIsolate()->GetCurrentContext(); + auto maybe_resolver = Promise::Resolver::New(context); + if (!maybe_resolver.IsEmpty()) + args.GetReturnValue().Set(maybe_resolver.ToLocalChecked()); +} + + +void PromiseResolve(const FunctionCallbackInfo& args) { + Local context = args.GetIsolate()->GetCurrentContext(); + Local promise = args[0]; + CHECK(promise->IsPromise()); + if (promise.As()->State() != Promise::kPending) return; + Local resolver = promise.As(); // sic + Maybe ret = resolver->Resolve(context, args[1]); + args.GetReturnValue().Set(ret.FromMaybe(false)); +} + + +void PromiseReject(const FunctionCallbackInfo& args) { + Local context = args.GetIsolate()->GetCurrentContext(); + Local promise = args[0]; + CHECK(promise->IsPromise()); + if (promise.As()->State() != Promise::kPending) return; + Local resolver = promise.As(); // sic + Maybe ret = resolver->Reject(context, args[1]); + args.GetReturnValue().Set(ret.FromMaybe(false)); +} + + void Initialize(Local target, Local unused, Local context) { @@ -192,6 +224,10 @@ void Initialize(Local target, env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog); env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog); env->SetMethod(target, "watchdogHasPendingSigint", WatchdogHasPendingSigint); + + env->SetMethod(target, "createPromise", CreatePromise); + env->SetMethod(target, "promiseResolve", PromiseResolve); + env->SetMethod(target, "promiseReject", PromiseReject); } } // namespace util diff --git a/test/internet/test-dns-ipv4.js b/test/internet/test-dns-ipv4.js index c4f2d00df886d9..4d4c7da6a7026d 100644 --- a/test/internet/test-dns-ipv4.js +++ b/test/internet/test-dns-ipv4.js @@ -3,8 +3,11 @@ const common = require('../common'); const assert = require('assert'); const dns = require('dns'); const net = require('net'); +const util = require('util'); const isIPv4 = net.isIPv4; +common.crashOnUnhandledRejection(); + let running = false; const queue = []; @@ -184,3 +187,13 @@ TEST(function test_lookupservice_ip_ipv4(done) { checkWrap(req); }); + +TEST(function test_lookupservice_ip_ipv4_promise(done) { + util.promisify(dns.lookupService)('127.0.0.1', 80) + .then(common.mustCall(({hostname, service}) => { + assert.strictEqual(typeof hostname, 'string'); + assert(hostname.length > 0); + assert(['http', 'www', '80'].includes(service)); + done(); + })); +}); diff --git a/test/internet/test-dns.js b/test/internet/test-dns.js index c927e1e665b217..269ba58b703e22 100644 --- a/test/internet/test-dns.js +++ b/test/internet/test-dns.js @@ -28,6 +28,8 @@ const isIPv4 = net.isIPv4; const isIPv6 = net.isIPv6; const util = require('util'); +common.crashOnUnhandledRejection(); + let expected = 0; let completed = 0; let running = false; @@ -428,6 +430,32 @@ TEST(function test_lookup_ip_all(done) { }); +TEST(function test_lookup_ip_all_promise(done) { + const req = util.promisify(dns.lookup)('127.0.0.1', {all: true}) + .then(function(ips) { + assert.ok(Array.isArray(ips)); + assert.ok(ips.length > 0); + assert.strictEqual(ips[0].address, '127.0.0.1'); + assert.strictEqual(ips[0].family, 4); + + done(); + }); + + checkWrap(req); +}); + + +TEST(function test_lookup_ip_promise(done) { + util.promisify(dns.lookup)('127.0.0.1') + .then(function({ address, family }) { + assert.strictEqual(address, '127.0.0.1'); + assert.strictEqual(family, 4); + + done(); + }); +}); + + TEST(function test_lookup_null_all(done) { const req = dns.lookup(null, {all: true}, function(err, ips, family) { assert.ifError(err); diff --git a/test/parallel/test-child-process-promisified.js b/test/parallel/test-child-process-promisified.js new file mode 100644 index 00000000000000..322cb110eb619a --- /dev/null +++ b/test/parallel/test-child-process-promisified.js @@ -0,0 +1,34 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const child_process = require('child_process'); +const { promisify } = require('util'); + +common.crashOnUnhandledRejection(); + +const exec = promisify(child_process.exec); +const execFile = promisify(child_process.execFile); + +{ + exec(`${process.execPath} -p 42`).then(common.mustCall((obj) => { + assert.deepStrictEqual(obj, { stdout: '42\n', stderr: '' }); + })); +} + +{ + execFile(process.execPath, ['-p', '42']).then(common.mustCall((obj) => { + assert.deepStrictEqual(obj, { stdout: '42\n', stderr: '' }); + })); +} + +{ + exec('doesntexist').catch(common.mustCall((err) => { + assert(err.message.includes('doesntexist')); + })); +} + +{ + execFile('doesntexist', ['-p', '42']).catch(common.mustCall((err) => { + assert(err.message.includes('doesntexist')); + })); +} diff --git a/test/parallel/test-fs-promisified.js b/test/parallel/test-fs-promisified.js new file mode 100644 index 00000000000000..12bd5d6fa1f954 --- /dev/null +++ b/test/parallel/test-fs-promisified.js @@ -0,0 +1,31 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const { promisify } = require('util'); + +common.crashOnUnhandledRejection(); + +const read = promisify(fs.read); +const write = promisify(fs.write); + +{ + const fd = fs.openSync(__filename, 'r'); + read(fd, Buffer.alloc(1024), 0, 1024, null).then(common.mustCall((obj) => { + assert.strictEqual(typeof obj.bytesRead, 'number'); + assert(obj.buffer instanceof Buffer); + fs.closeSync(fd); + })); +} + +common.refreshTmpDir(); +{ + const filename = path.join(common.tmpDir, 'write-promise.txt'); + const fd = fs.openSync(filename, 'w'); + write(fd, Buffer.from('foobar')).then(common.mustCall((obj) => { + assert.strictEqual(typeof obj.bytesWritten, 'number'); + assert.strictEqual(obj.buffer.toString(), 'foobar'); + fs.closeSync(fd); + })); +} diff --git a/test/parallel/test-promise-internal-creation.js b/test/parallel/test-promise-internal-creation.js new file mode 100644 index 00000000000000..7142c872d9452e --- /dev/null +++ b/test/parallel/test-promise-internal-creation.js @@ -0,0 +1,41 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const { + createPromise, promiseResolve, promiseReject +} = process.binding('util'); +const { inspect } = require('util'); + +common.crashOnUnhandledRejection(); + +{ + const promise = createPromise(); + assert.strictEqual(inspect(promise), 'Promise { }'); + promiseResolve(promise, 42); + assert.strictEqual(inspect(promise), 'Promise { 42 }'); + promise.then(common.mustCall((value) => { + assert.strictEqual(value, 42); + })); +} + +{ + const promise = createPromise(); + const error = new Error('foobar'); + promiseReject(promise, error); + assert(inspect(promise).includes(' Error: foobar')); + promise.catch(common.mustCall((value) => { + assert.strictEqual(value, error); + })); +} + +{ + const promise = createPromise(); + const error = new Error('foobar'); + promiseReject(promise, error); + assert(inspect(promise).includes(' Error: foobar')); + promiseResolve(promise, 42); + assert(inspect(promise).includes(' Error: foobar')); + promise.catch(common.mustCall((value) => { + assert.strictEqual(value, error); + })); +} diff --git a/test/parallel/test-timers-promisified.js b/test/parallel/test-timers-promisified.js new file mode 100644 index 00000000000000..1dad1d8cfc0d1c --- /dev/null +++ b/test/parallel/test-timers-promisified.js @@ -0,0 +1,40 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const timers = require('timers'); +const { promisify } = require('util'); + +/* eslint-disable no-restricted-syntax */ + +common.crashOnUnhandledRejection(); + +const setTimeout = promisify(timers.setTimeout); +const setImmediate = promisify(timers.setImmediate); + +{ + const promise = setTimeout(1); + promise.then(common.mustCall((value) => { + assert.strictEqual(value, undefined); + })); +} + +{ + const promise = setTimeout(1, 'foobar'); + promise.then(common.mustCall((value) => { + assert.strictEqual(value, 'foobar'); + })); +} + +{ + const promise = setImmediate(); + promise.then(common.mustCall((value) => { + assert.strictEqual(value, undefined); + })); +} + +{ + const promise = setImmediate('foobar'); + promise.then(common.mustCall((value) => { + assert.strictEqual(value, 'foobar'); + })); +} diff --git a/test/parallel/test-util-promisify.js b/test/parallel/test-util-promisify.js new file mode 100644 index 00000000000000..84919714154b28 --- /dev/null +++ b/test/parallel/test-util-promisify.js @@ -0,0 +1,187 @@ +'use strict'; +// Flags: --expose-internals +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const vm = require('vm'); +const { promisify } = require('util'); +const { customPromisifyArgs } = require('internal/util'); + +common.crashOnUnhandledRejection(); + +const stat = promisify(fs.stat); + +{ + const promise = stat(__filename); + assert(promise instanceof Promise); + promise.then(common.mustCall((value) => { + assert.deepStrictEqual(value, fs.statSync(__filename)); + })); +} + +{ + const promise = stat('/dontexist'); + promise.catch(common.mustCall((error) => { + assert(error.message.includes('ENOENT: no such file or directory, stat')); + })); +} + +{ + function fn() {} + function promisifedFn() {} + fn[promisify.custom] = promisifedFn; + assert.strictEqual(promisify(fn), promisifedFn); + assert.strictEqual(promisify(promisify(fn)), promisifedFn); +} + +{ + function fn() {} + fn[promisify.custom] = 42; + assert.throws( + () => promisify(fn), + (err) => err instanceof TypeError && + err.message === 'The [util.promisify.custom] property must ' + + 'be a function'); +} + +{ + const firstValue = 5; + const secondValue = 17; + + function fn(callback) { + callback(null, firstValue, secondValue); + } + + fn[customPromisifyArgs] = ['first', 'second']; + + promisify(fn)().then(common.mustCall((obj) => { + assert.deepStrictEqual(obj, {first: firstValue, second: secondValue}); + })); +} + +{ + const fn = vm.runInNewContext('(function() {})'); + assert.notStrictEqual(Object.getPrototypeOf(promisify(fn)), + Function.prototype); +} + +{ + function fn(callback) { + callback(null, 'foo', 'bar'); + } + promisify(fn)().then(common.mustCall((value) => { + assert.deepStrictEqual(value, 'foo'); + })); +} + +{ + function fn(callback) { + callback(null); + } + promisify(fn)().then(common.mustCall((value) => { + assert.strictEqual(value, undefined); + })); +} + +{ + function fn(callback) { + callback(); + } + promisify(fn)().then(common.mustCall((value) => { + assert.strictEqual(value, undefined); + })); +} + +{ + function fn(err, val, callback) { + callback(err, val); + } + promisify(fn)(null, 42).then(common.mustCall((value) => { + assert.strictEqual(value, 42); + })); +} + +{ + function fn(err, val, callback) { + callback(err, val); + } + promisify(fn)(new Error('oops'), null).catch(common.mustCall((err) => { + assert.strictEqual(err.message, 'oops'); + })); +} + +{ + function fn(err, val, callback) { + callback(err, val); + } + + (async () => { + const value = await promisify(fn)(null, 42); + assert.strictEqual(value, 42); + })(); +} + +{ + const o = {}; + const fn = promisify(function(cb) { + + cb(null, this === o); + }); + + o.fn = fn; + + o.fn().then(common.mustCall(function(val) { + assert(val); + })); +} + +{ + const err = new Error('Should not have called the callback with the error.'); + const stack = err.stack; + + const fn = promisify(function(cb) { + cb(null); + cb(err); + }); + + (async () => { + await fn(); + await Promise.resolve(); + return assert.strictEqual(stack, err.stack); + })(); +} + +{ + function c() { } + const a = promisify(function() { }); + const b = promisify(a); + assert.notStrictEqual(c, a); + assert.strictEqual(a, b); +} + +{ + let errToThrow; + const thrower = promisify(function(a, b, c, cb) { + errToThrow = new Error(); + throw errToThrow; + }); + thrower(1, 2, 3) + .then(assert.fail) + .then(assert.fail, (e) => assert.strictEqual(e, errToThrow)); +} + +{ + const err = new Error(); + + const a = promisify((cb) => cb(err))(); + const b = promisify(() => { throw err; })(); + + Promise.all([ + a.then(assert.fail, function(e) { + assert.strictEqual(err, e); + }), + b.then(assert.fail, function(e) { + assert.strictEqual(err, e); + }) + ]); +}