Skip to content

Commit 2c65d29

Browse files
Merge pull request #25 from defunctzombie/promisify-callbackify
Add util.promisify and util.callbackify
2 parents 0fe5860 + aafbe16 commit 2c65d29

File tree

10 files changed

+1039
-1
lines changed

10 files changed

+1039
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"license": "MIT",
3030
"devDependencies": {
3131
"airtap": "0.0.6",
32+
"is-async-supported": "~1.2.0",
3233
"run-series": "~1.1.4",
3334
"tape": "~4.9.0"
3435
},

test/browser/callbackify.js

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
'use strict';
2+
3+
var test = require('tape');
4+
var callbackify = require('../../').callbackify;
5+
6+
if (typeof Promise === 'undefined') {
7+
console.log('no global Promise found, skipping callbackify tests');
8+
return;
9+
}
10+
11+
function after (n, cb) {
12+
var i = 0;
13+
return function () {
14+
if (++i === n) cb();
15+
}
16+
}
17+
18+
var values = [
19+
'hello world',
20+
null,
21+
undefined,
22+
false,
23+
0,
24+
{},
25+
{ key: 'value' },
26+
function ok() {},
27+
['array', 'with', 4, 'values'],
28+
new Error('boo')
29+
];
30+
if (typeof Symbol !== 'undefined') {
31+
values.push(Symbol('I am a symbol'));
32+
}
33+
34+
test('util.callbackify resolution value is passed as second argument to callback', function (t) {
35+
var end = after(values.length * 2, t.end);
36+
// Test that the resolution value is passed as second argument to callback
37+
values.forEach(function(value) {
38+
// Test Promise factory
39+
function promiseFn() {
40+
return Promise.resolve(value);
41+
}
42+
43+
var cbPromiseFn = callbackify(promiseFn);
44+
cbPromiseFn(function(err, ret) {
45+
t.ifError(err);
46+
t.strictEqual(ret, value, 'cb ' + typeof value);
47+
end();
48+
});
49+
50+
// Test Thenable
51+
function thenableFn() {
52+
return {
53+
then: function(onRes, onRej) {
54+
onRes(value);
55+
}
56+
};
57+
}
58+
59+
var cbThenableFn = callbackify(thenableFn);
60+
cbThenableFn(function(err, ret) {
61+
t.ifError(err);
62+
t.strictEqual(ret, value, 'thenable ' + typeof value);
63+
end();
64+
});
65+
});
66+
});
67+
68+
test('util.callbackify rejection reason is passed as first argument to callback', function (t) {
69+
var end = after(values.length * 2, t.end);
70+
// Test that rejection reason is passed as first argument to callback
71+
values.forEach(function(value) {
72+
// test a Promise factory
73+
function promiseFn() {
74+
return Promise.reject(value);
75+
}
76+
77+
var cbPromiseFn = callbackify(promiseFn);
78+
cbPromiseFn(function(err, ret) {
79+
t.strictEqual(ret, undefined, 'cb ' + typeof value);
80+
if (err instanceof Error) {
81+
if ('reason' in err) {
82+
t.ok(!value);
83+
t.strictEqual(err.message, 'Promise was rejected with a falsy value');
84+
t.strictEqual(err.reason, value);
85+
} else {
86+
t.strictEqual(String(value).slice(-err.message.length), err.message);
87+
}
88+
} else {
89+
t.strictEqual(err, value);
90+
}
91+
end();
92+
});
93+
94+
// Test Thenable
95+
function thenableFn() {
96+
return {
97+
then: function (onRes, onRej) {
98+
onRej(value);
99+
}
100+
};
101+
}
102+
103+
var cbThenableFn = callbackify(thenableFn);
104+
cbThenableFn(function(err, ret) {
105+
t.strictEqual(ret, undefined, 'thenable ' + typeof value);
106+
if (err instanceof Error) {
107+
if ('reason' in err) {
108+
t.ok(!value);
109+
t.strictEqual(err.message, 'Promise was rejected with a falsy value');
110+
t.strictEqual(err.reason, value);
111+
} else {
112+
t.strictEqual(String(value).slice(-err.message.length), err.message);
113+
}
114+
} else {
115+
t.strictEqual(err, value);
116+
}
117+
end();
118+
});
119+
});
120+
});
121+
122+
test('util.callbackify arguments passed to callbackified function are passed to original', function (t) {
123+
var end = after(values.length, t.end);
124+
// Test that arguments passed to callbackified function are passed to original
125+
values.forEach(function(value) {
126+
function promiseFn(arg) {
127+
t.strictEqual(arg, value);
128+
return Promise.resolve(arg);
129+
}
130+
131+
var cbPromiseFn = callbackify(promiseFn);
132+
cbPromiseFn(value, function(err, ret) {
133+
t.ifError(err);
134+
t.strictEqual(ret, value);
135+
end();
136+
});
137+
});
138+
});
139+
140+
test('util.callbackify `this` binding is the same for callbackified and original', function (t) {
141+
var end = after(values.length, t.end);
142+
// Test that `this` binding is the same for callbackified and original
143+
values.forEach(function(value) {
144+
var iAmThis = {
145+
fn: function(arg) {
146+
t.strictEqual(this, iAmThis);
147+
return Promise.resolve(arg);
148+
},
149+
};
150+
iAmThis.cbFn = callbackify(iAmThis.fn);
151+
iAmThis.cbFn(value, function(err, ret) {
152+
t.ifError(err);
153+
t.strictEqual(ret, value);
154+
t.strictEqual(this, iAmThis);
155+
end();
156+
});
157+
});
158+
});
159+
160+
test('util.callbackify non-function inputs throw', function (t) {
161+
// Verify that non-function inputs throw.
162+
['foo', null, undefined, false, 0, {}, typeof Symbol !== 'undefined' ? Symbol() : undefined, []].forEach(function(value) {
163+
t.throws(
164+
function() { callbackify(value); },
165+
'The "original" argument must be of type Function'
166+
);
167+
});
168+
t.end();
169+
});

test/browser/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
require('./inspect');
22
require('./is');
3+
require('./promisify');
4+
require('./callbackify');

test/browser/promisify.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
var promisify = require('../../').promisify;
2+
var test = require('tape');
3+
4+
var hasSymbol = typeof Symbol !== 'undefined';
5+
6+
if (typeof Promise === 'undefined') {
7+
console.log('no global Promise found, skipping promisify tests');
8+
return;
9+
}
10+
11+
var callbacker = function (arg, cb) {
12+
setTimeout(function () {
13+
if (typeof arg === 'string')
14+
cb(null, arg.toUpperCase());
15+
else cb(new TypeError('incorrect type'));
16+
}, 5);
17+
}
18+
var promiser = promisify(callbacker);
19+
20+
test('util.promisify resolves', function (t) {
21+
var promise = promiser(__filename);
22+
t.ok(promise instanceof Promise);
23+
promise.then(function (value) {
24+
t.deepEqual(value, __filename.toUpperCase());
25+
t.end();
26+
});
27+
});
28+
29+
test('util.promisify rejects', function (t) {
30+
var promise = promiser(42);
31+
promise.catch(function (error) {
32+
t.equal(error.message, 'incorrect type');
33+
t.end();
34+
});
35+
});
36+
37+
test('util.promisify custom', { skip: !hasSymbol }, function (t) {
38+
function fn() {}
39+
function promisifedFn() {}
40+
fn[promisify.custom] = promisifedFn;
41+
t.strictEqual(promisify(fn), promisifedFn);
42+
t.strictEqual(promisify(promisify(fn)), promisifedFn);
43+
t.end();
44+
});
45+
46+
test('util.promisify custom of invalid type', { skip: !hasSymbol }, function (t) {
47+
function fn2() {}
48+
fn2[promisify.custom] = 42;
49+
t.throws(
50+
function () { promisify(fn2); },
51+
/must be of type Function/
52+
);
53+
t.end();
54+
});
55+
56+
test('util.promisify multiple callback values', function (t) {
57+
function fn5(callback) {
58+
callback(null, 'foo', 'bar');
59+
}
60+
promisify(fn5)().then(function (value) {
61+
t.strictEqual(value, 'foo');
62+
t.end();
63+
});
64+
});
65+
66+
test('util.promisify no callback success value', function (t) {
67+
function fn6(callback) {
68+
callback(null);
69+
}
70+
promisify(fn6)().then(function (value) {
71+
t.strictEqual(value, undefined);
72+
t.end();
73+
});
74+
});
75+
76+
test('util.promisify no callback arguments at all', function (t) {
77+
function fn7(callback) {
78+
callback();
79+
}
80+
promisify(fn7)().then(function (value) {
81+
t.strictEqual(value, undefined);
82+
t.end();
83+
});
84+
});
85+
86+
test('util.promisify passing arguments', function (t) {
87+
function fn8(err, val, callback) {
88+
callback(err, val);
89+
}
90+
promisify(fn8)(null, 42).then(function (value) {
91+
t.strictEqual(value, 42);
92+
t.end();
93+
});
94+
});
95+
96+
test('util.promisify passing arguments (rejects)', function (t) {
97+
function fn9(err, val, callback) {
98+
callback(err, val);
99+
}
100+
promisify(fn9)(new Error('oops'), null).catch(function (err) {
101+
t.strictEqual(err.message, 'oops');
102+
t.end();
103+
});
104+
});
105+
106+
test('util.promisify chain', function (t) {
107+
function fn9(err, val, callback) {
108+
callback(err, val);
109+
}
110+
111+
112+
Promise.resolve()
113+
.then(function () { return promisify(fn9)(null, 42); })
114+
.then(function (value) {
115+
t.strictEqual(value, 42);
116+
t.end();
117+
});
118+
});
119+
120+
test('util.promisify keeps correct `this`', function (t) {
121+
var o = {};
122+
var fn10 = promisify(function(cb) {
123+
124+
cb(null, this === o);
125+
});
126+
127+
o.fn = fn10;
128+
129+
o.fn().then(function(val) {
130+
t.ok(val);
131+
t.end();
132+
});
133+
});
134+
135+
test('util.promisify calling callback multiple times', function (t) {
136+
var err = new Error('Should not have called the callback with the error.');
137+
var stack = err.stack;
138+
139+
var fn11 = promisify(function(cb) {
140+
cb(null);
141+
cb(err);
142+
});
143+
144+
Promise.resolve()
145+
.then(function () { return fn11(); })
146+
.then(function () { return Promise.resolve(); })
147+
.then(function () {
148+
t.strictEqual(stack, err.stack);
149+
t.end();
150+
});
151+
});
152+
153+
// Can't do this without Symbol() unfortunately
154+
test('util.promisify promisifying a promisified function', { skip: !hasSymbol }, function (t) {
155+
function c() { }
156+
var a = promisify(function() { });
157+
var b = promisify(a);
158+
t.notStrictEqual(c, a);
159+
t.strictEqual(a, b);
160+
t.end();
161+
});
162+
163+
test('util.promisify sync throw becomes rejection', function (t) {
164+
var errToThrow;
165+
var thrower = promisify(function(a, b, c, cb) {
166+
errToThrow = new Error();
167+
throw errToThrow;
168+
});
169+
thrower(1, 2, 3)
170+
.then(t.fail)
171+
.then(t.fail, function (e) {
172+
t.strictEqual(e, errToThrow);
173+
t.end();
174+
});
175+
});
176+
177+
test('util.promisify callback and sync throw', function (t) {
178+
var err = new Error();
179+
180+
var a = promisify(function (cb) { cb(err) })();
181+
var b = promisify(function () { throw err; })();
182+
183+
Promise.all([
184+
a.then(t.fail, function(e) {
185+
t.strictEqual(err, e);
186+
}),
187+
b.then(t.fail, function(e) {
188+
t.strictEqual(err, e);
189+
})
190+
]).then(function () {
191+
t.end();
192+
});
193+
});
194+
195+
test('util.promisify throws for incorrect argument types', function (t) {
196+
var array = [undefined, null, true, 0, 'str', {}, []];
197+
if (typeof Symbol !== 'undefined') array.push(Symbol());
198+
array.forEach(function (input) {
199+
t.throws(
200+
function () { promisify(input); },
201+
'The "original" argument must be of type Function'
202+
);
203+
});
204+
t.end();
205+
});

0 commit comments

Comments
 (0)