Skip to content

Commit e128d9d

Browse files
committed
Add util.promisify
1 parent 0fe5860 commit e128d9d

File tree

3 files changed

+288
-1
lines changed

3 files changed

+288
-1
lines changed

test/node/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ series([
1515
test(require.resolve('./debug')),
1616
test(require.resolve('./format')),
1717
test(require.resolve('./inspect')),
18-
test(require.resolve('./log'))
18+
test(require.resolve('./log')),
19+
test(require.resolve('./promisify'))
1920
]);

test/node/promisify.js

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
var assert = require('assert');
2+
var fs = require('fs');
3+
var vm = require('vm');
4+
var promisify = require('../../util').promisify;
5+
6+
if (typeof Promise === 'undefined') {
7+
console.log('no global Promise found, skipping promisify tests');
8+
return;
9+
}
10+
11+
var mustCalls = [];
12+
var common = {
13+
expectsError: function (fn, props) {
14+
try { fn(); }
15+
catch (err) {
16+
if (props.type) assert.equal(err.constructor, props.type);
17+
if (props.message) assert.equal(err.message, props.message);
18+
return;
19+
}
20+
assert.fail('expected error');
21+
},
22+
mustCall: function (fn) {
23+
function mustCall() {
24+
mustCall.called = true
25+
return fn.apply(this, arguments);
26+
}
27+
28+
mustCalls.push(mustCall);
29+
return mustCall;
30+
}
31+
};
32+
33+
var stat = promisify(fs.stat);
34+
35+
{
36+
var promise = stat(__filename);
37+
assert(promise instanceof Promise);
38+
promise.then(common.mustCall(function (value) {
39+
assert.deepStrictEqual(value, fs.statSync(__filename));
40+
}));
41+
}
42+
43+
{
44+
var promise = stat('/dontexist');
45+
promise.catch(common.mustCall(function (error) {
46+
assert(error.message.indexOf('ENOENT: no such file or directory, stat') !== -1);
47+
}));
48+
}
49+
50+
{
51+
function fn() {}
52+
function promisifedFn() {}
53+
fn[promisify.custom] = promisifedFn;
54+
assert.strictEqual(promisify(fn), promisifedFn);
55+
assert.strictEqual(promisify(promisify(fn)), promisifedFn);
56+
}
57+
58+
{
59+
function fn2() {}
60+
fn2[promisify.custom] = 42;
61+
common.expectsError(
62+
function () { promisify(fn2); },
63+
{ code: 'ERR_INVALID_ARG_TYPE', type: TypeError }
64+
);
65+
}
66+
67+
// promisify args test disabled, it is an internal core API that is
68+
// not used anywhere anymore and this package does not implement it.
69+
if (false) {
70+
var firstValue = 5;
71+
var secondValue = 17;
72+
73+
function fn3(callback) {
74+
callback(null, firstValue, secondValue);
75+
}
76+
77+
fn3[customPromisifyArgs] = ['first', 'second'];
78+
79+
promisify(fn3)().then(common.mustCall(function (obj) {
80+
assert.deepStrictEqual(obj, { first: firstValue, second: secondValue });
81+
}));
82+
}
83+
84+
{
85+
var fn4 = vm.runInNewContext('(function() {})');
86+
assert.notStrictEqual(Object.getPrototypeOf(promisify(fn4)),
87+
Function.prototype);
88+
}
89+
90+
{
91+
function fn5(callback) {
92+
callback(null, 'foo', 'bar');
93+
}
94+
promisify(fn5)().then(common.mustCall(function (value) {
95+
assert.deepStrictEqual(value, 'foo');
96+
}));
97+
}
98+
99+
{
100+
function fn6(callback) {
101+
callback(null);
102+
}
103+
promisify(fn6)().then(common.mustCall(function (value) {
104+
assert.strictEqual(value, undefined);
105+
}));
106+
}
107+
108+
{
109+
function fn7(callback) {
110+
callback();
111+
}
112+
promisify(fn7)().then(common.mustCall(function (value) {
113+
assert.strictEqual(value, undefined);
114+
}));
115+
}
116+
117+
{
118+
function fn8(err, val, callback) {
119+
callback(err, val);
120+
}
121+
promisify(fn8)(null, 42).then(common.mustCall(function (value) {
122+
assert.strictEqual(value, 42);
123+
}));
124+
}
125+
126+
{
127+
function fn9(err, val, callback) {
128+
callback(err, val);
129+
}
130+
promisify(fn9)(new Error('oops'), null).catch(common.mustCall(function (err) {
131+
assert.strictEqual(err.message, 'oops');
132+
}));
133+
}
134+
135+
{
136+
function fn9(err, val, callback) {
137+
callback(err, val);
138+
}
139+
140+
141+
Promise.resolve()
142+
.then(function () { return promisify(fn9)(null, 42); })
143+
.then(function (value) {
144+
assert.strictEqual(value, 42);
145+
});
146+
}
147+
148+
{
149+
var o = {};
150+
var fn10 = promisify(function(cb) {
151+
152+
cb(null, this === o);
153+
});
154+
155+
o.fn = fn10;
156+
157+
o.fn().then(common.mustCall(function(val) {
158+
assert(val);
159+
}));
160+
}
161+
162+
(function () {
163+
var err = new Error('Should not have called the callback with the error.');
164+
var stack = err.stack;
165+
166+
var fn11 = promisify(function(cb) {
167+
cb(null);
168+
cb(err);
169+
});
170+
171+
Promise.resolve()
172+
.then(function () { return fn11(); })
173+
.then(function () { return Promise.resolve(); })
174+
.then(function () {
175+
assert.strictEqual(stack, err.stack);
176+
});
177+
})();
178+
179+
{
180+
function c() { }
181+
var a = promisify(function() { });
182+
var b = promisify(a);
183+
assert.notStrictEqual(c, a);
184+
assert.strictEqual(a, b);
185+
}
186+
187+
{
188+
var errToThrow;
189+
var thrower = promisify(function(a, b, c, cb) {
190+
errToThrow = new Error();
191+
throw errToThrow;
192+
});
193+
thrower(1, 2, 3)
194+
.then(assert.fail)
195+
.then(assert.fail, function (e) { assert.strictEqual(e, errToThrow); });
196+
}
197+
198+
{
199+
var err = new Error();
200+
201+
var a = promisify(function (cb) { cb(err) })();
202+
var b = promisify(function () { throw err; })();
203+
204+
Promise.all([
205+
a.then(assert.fail, function(e) {
206+
assert.strictEqual(err, e);
207+
}),
208+
b.then(assert.fail, function(e) {
209+
assert.strictEqual(err, e);
210+
})
211+
]);
212+
}
213+
214+
[undefined, null, true, 0, 'str', {}, [], Symbol()].forEach(function (input) {
215+
common.expectsError(
216+
function () { promisify(input); },
217+
{
218+
code: 'ERR_INVALID_ARG_TYPE',
219+
type: TypeError,
220+
message: 'The "original" argument must be of type Function'
221+
});
222+
});
223+
224+
process.on('exit', function () {
225+
mustCalls.forEach(function (mc) {
226+
assert(mc.called);
227+
});
228+
});

util.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,3 +584,61 @@ exports._extend = function(origin, add) {
584584
function hasOwnProperty(obj, prop) {
585585
return Object.prototype.hasOwnProperty.call(obj, prop);
586586
}
587+
588+
var kCustomPromisifiedSymbol = typeof Symbol !== 'undefined' ? Symbol('util.promisify.custom') : undefined;
589+
590+
exports.promisify = function promisify(original) {
591+
if (typeof original !== 'function')
592+
throw new TypeError('The "original" argument must be of type Function');
593+
594+
if (kCustomPromisifiedSymbol && original[kCustomPromisifiedSymbol]) {
595+
var fn = original[kCustomPromisifiedSymbol];
596+
if (typeof fn !== 'function') {
597+
throw new TypeError('The "util.promisify.custom" argument must be of type Function');
598+
}
599+
Object.defineProperty(fn, kCustomPromisifiedSymbol, {
600+
value: fn, enumerable: false, writable: false, configurable: true
601+
});
602+
return fn;
603+
}
604+
605+
function fn() {
606+
var promiseResolve, promiseReject;
607+
var promise = new Promise(function (resolve, reject) {
608+
promiseResolve = resolve;
609+
promiseReject = reject;
610+
});
611+
612+
var args = [];
613+
for (var i = 0; i < arguments.length; i++) {
614+
args.push(arguments[i]);
615+
}
616+
args.push(function (err, value) {
617+
if (err) {
618+
promiseReject(err);
619+
} else {
620+
promiseResolve(value);
621+
}
622+
});
623+
624+
try {
625+
original.apply(this, args);
626+
} catch (err) {
627+
promiseReject(err);
628+
}
629+
630+
return promise;
631+
}
632+
633+
Object.setPrototypeOf(fn, Object.getPrototypeOf(original));
634+
635+
if (kCustomPromisifiedSymbol) Object.defineProperty(fn, kCustomPromisifiedSymbol, {
636+
value: fn, enumerable: false, writable: false, configurable: true
637+
});
638+
return Object.defineProperties(
639+
fn,
640+
Object.getOwnPropertyDescriptors(original)
641+
);
642+
}
643+
644+
exports.promisify.custom = kCustomPromisifiedSymbol

0 commit comments

Comments
 (0)