Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 99da8e8

Browse filesBrowse files
committed
util: add util.promisify()
Add `util.promisify(function)` for creating promisified functions. Includes documentation and tests. Fixes: nodejs/CTC#12 PR-URL: #12442 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Myles Borins <myles.borins@gmail.com> Reviewed-By: Evan Lucas <evanlucas@me.com> Reviewed-By: William Kapke <william.kapke@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Teddy Katz <teddy.katz@gmail.com>
1 parent 059f296 commit 99da8e8
Copy full SHA for 99da8e8

File tree

Expand file treeCollapse file tree

5 files changed

+222
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

5 files changed

+222
-0
lines changed
Open diff view settings
Collapse file

‎doc/api/util.md‎

Copy file name to clipboardExpand all lines: doc/api/util.md
+82Lines changed: 82 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,86 @@ util.inspect.defaultOptions.maxArrayLength = null;
399399
console.log(arr); // logs the full array
400400
```
401401

402+
## util.promisify(original)
403+
<!-- YAML
404+
added: REPLACEME
405+
-->
406+
407+
* `original` {Function}
408+
409+
Takes a function following the common Node.js callback style, i.e. taking a
410+
`(err, value) => ...` callback as the last argument, and returns a version
411+
that returns promises.
412+
413+
For example:
414+
415+
```js
416+
const util = require('util');
417+
const fs = require('fs');
418+
419+
const stat = util.promisify(fs.stat);
420+
stat('.').then((stats) => {
421+
// Do something with `stats`
422+
}).catch((error) => {
423+
// Handle the error.
424+
});
425+
```
426+
427+
Or, equivalently using `async function`s:
428+
429+
```js
430+
const util = require('util');
431+
const fs = require('fs');
432+
433+
const stat = util.promisify(fs.stat);
434+
435+
async function callStat() {
436+
const stats = await stat('.');
437+
console.log(`This directory is owned by ${stats.uid}`);
438+
}
439+
```
440+
441+
If there is an `original[util.promisify.custom]` property present, `promisify`
442+
will return its value, see [Custom promisified functions][].
443+
444+
`promisify()` assumes that `original` is a function taking a callback as its
445+
final argument in all cases, and the returned function will result in undefined
446+
behaviour if it does not.
447+
448+
### Custom promisified functions
449+
450+
Using the `util.promisify.custom` symbol one can override the return value of
451+
[`util.promisify()`][]:
452+
453+
```js
454+
const util = require('util');
455+
456+
function doSomething(foo, callback) {
457+
// ...
458+
}
459+
460+
doSomething[util.promisify.custom] = function(foo) {
461+
return getPromiseSomehow();
462+
};
463+
464+
const promisified = util.promisify(doSomething);
465+
console.log(promisified === doSomething[util.promisify.custom]);
466+
// prints 'true'
467+
```
468+
469+
This can be useful for cases where the original function does not follow the
470+
standard format of taking an error-first callback as the last argument.
471+
472+
### util.promisify.custom
473+
<!-- YAML
474+
added: REPLACEME
475+
-->
476+
477+
* {symbol}
478+
479+
A Symbol that can be used to declare custom promisified variants of functions,
480+
see [Custom promisified functions][].
481+
402482
## Deprecated APIs
403483

404484
The following APIs have been deprecated and should no longer be used. Existing
@@ -878,7 +958,9 @@ Deprecated predecessor of `console.log`.
878958
[`console.error()`]: console.html#console_console_error_data_args
879959
[`console.log()`]: console.html#console_console_log_data_args
880960
[`util.inspect()`]: #util_util_inspect_object_options
961+
[`util.promisify()`]: #util_util_promisify_original
881962
[Custom inspection functions on Objects]: #util_custom_inspection_functions_on_objects
882963
[Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors
964+
[Custom promisified functions]: #util_custom_promisified_functions
883965
[constructor]: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor
884966
[semantically incompatible]: https://github.com/nodejs/node/issues/4179
Collapse file

‎lib/internal/util.js‎

Copy file name to clipboardExpand all lines: lib/internal/util.js
+61Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const errors = require('internal/errors');
44
const binding = process.binding('util');
55
const signals = process.binding('constants').os.signals;
66

7+
const { createPromise, promiseResolve, promiseReject } = binding;
8+
79
const kArrowMessagePrivateSymbolIndex = binding['arrow_message_private_symbol'];
810
const kDecoratedPrivateSymbolIndex = binding['decorated_private_symbol'];
911
const noCrypto = !process.versions.openssl;
@@ -217,3 +219,62 @@ module.exports = exports = {
217219
// default isEncoding implementation, just in case userland overrides it.
218220
kIsEncodingSymbol: Symbol('node.isEncoding')
219221
};
222+
223+
const kCustomPromisifiedSymbol = Symbol('util.promisify.custom');
224+
const kCustomPromisifyArgsSymbol = Symbol('customPromisifyArgs');
225+
226+
function promisify(orig) {
227+
if (typeof orig !== 'function') {
228+
const errors = require('internal/errors');
229+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'original', 'function');
230+
}
231+
232+
if (orig[kCustomPromisifiedSymbol]) {
233+
const fn = orig[kCustomPromisifiedSymbol];
234+
if (typeof fn !== 'function') {
235+
throw new TypeError('The [util.promisify.custom] property must be ' +
236+
'a function');
237+
}
238+
Object.defineProperty(fn, kCustomPromisifiedSymbol, {
239+
value: fn, enumerable: false, writable: false, configurable: true
240+
});
241+
return fn;
242+
}
243+
244+
// Names to create an object from in case the callback receives multiple
245+
// arguments, e.g. ['stdout', 'stderr'] for child_process.exec.
246+
const argumentNames = orig[kCustomPromisifyArgsSymbol];
247+
248+
function fn(...args) {
249+
const promise = createPromise();
250+
try {
251+
orig.call(this, ...args, (err, ...values) => {
252+
if (err) {
253+
promiseReject(promise, err);
254+
} else if (argumentNames !== undefined && values.length > 1) {
255+
const obj = {};
256+
for (var i = 0; i < argumentNames.length; i++)
257+
obj[argumentNames[i]] = values[i];
258+
promiseResolve(promise, obj);
259+
} else {
260+
promiseResolve(promise, values[0]);
261+
}
262+
});
263+
} catch (err) {
264+
promiseReject(promise, err);
265+
}
266+
return promise;
267+
}
268+
269+
Object.setPrototypeOf(fn, Object.getPrototypeOf(orig));
270+
271+
Object.defineProperty(fn, kCustomPromisifiedSymbol, {
272+
value: fn, enumerable: false, writable: false, configurable: true
273+
});
274+
return Object.defineProperties(fn, Object.getOwnPropertyDescriptors(orig));
275+
}
276+
277+
promisify.custom = kCustomPromisifiedSymbol;
278+
279+
exports.promisify = promisify;
280+
exports.customPromisifyArgs = kCustomPromisifyArgsSymbol;
Collapse file

‎lib/util.js‎

Copy file name to clipboardExpand all lines: lib/util.js
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,3 +1057,5 @@ exports._exceptionWithHostPort = function(err,
10571057
// process.versions needs a custom function as some values are lazy-evaluated.
10581058
process.versions[exports.inspect.custom] =
10591059
(depth) => exports.format(JSON.parse(JSON.stringify(process.versions)));
1060+
1061+
exports.promisify = internalUtil.promisify;
Collapse file

‎src/node_util.cc‎

Copy file name to clipboardExpand all lines: src/node_util.cc
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ using v8::Value;
2121

2222

2323
#define VALUE_METHOD_MAP(V) \
24+
V(isAsyncFunction, IsAsyncFunction) \
2425
V(isDataView, IsDataView) \
2526
V(isDate, IsDate) \
2627
V(isExternal, IsExternal) \
Collapse file
+76Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const fs = require('fs');
5+
const vm = require('vm');
6+
const { promisify } = require('util');
7+
8+
common.crashOnUnhandledRejection();
9+
10+
const stat = promisify(fs.stat);
11+
12+
{
13+
const promise = stat(__filename);
14+
assert(promise instanceof Promise);
15+
promise.then(common.mustCall((value) => {
16+
assert.deepStrictEqual(value, fs.statSync(__filename));
17+
}));
18+
}
19+
20+
{
21+
const promise = stat('/dontexist');
22+
promise.catch(common.mustCall((error) => {
23+
assert(error.message.includes('ENOENT: no such file or directory, stat'));
24+
}));
25+
}
26+
27+
{
28+
function fn() {}
29+
function promisifedFn() {}
30+
fn[promisify.custom] = promisifedFn;
31+
assert.strictEqual(promisify(fn), promisifedFn);
32+
assert.strictEqual(promisify(promisify(fn)), promisifedFn);
33+
}
34+
35+
{
36+
function fn() {}
37+
fn[promisify.custom] = 42;
38+
assert.throws(
39+
() => promisify(fn),
40+
(err) => err instanceof TypeError &&
41+
err.message === 'The [util.promisify.custom] property must ' +
42+
'be a function');
43+
}
44+
45+
{
46+
const fn = vm.runInNewContext('(function() {})');
47+
assert.notStrictEqual(Object.getPrototypeOf(promisify(fn)),
48+
Function.prototype);
49+
}
50+
51+
{
52+
function fn(callback) {
53+
callback(null, 'foo', 'bar');
54+
}
55+
promisify(fn)().then(common.mustCall((value) => {
56+
assert.deepStrictEqual(value, 'foo');
57+
}));
58+
}
59+
60+
{
61+
function fn(callback) {
62+
callback(null);
63+
}
64+
promisify(fn)().then(common.mustCall((value) => {
65+
assert.strictEqual(value, undefined);
66+
}));
67+
}
68+
69+
{
70+
function fn(callback) {
71+
callback();
72+
}
73+
promisify(fn)().then(common.mustCall((value) => {
74+
assert.strictEqual(value, undefined);
75+
}));
76+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.