Skip to content

Commit a5e4650

Browse files
committed
Add util.callbackify
1 parent e128d9d commit a5e4650

File tree

7 files changed

+394
-29
lines changed

7 files changed

+394
-29
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/node/callbackify-async.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
'use strict';
2+
3+
// Separate test file for tests using new syntax (async/await).
4+
5+
var common = require('./common');
6+
var assert = require('assert');
7+
var callbackify = require('../../').callbackify;
8+
var execFile = require('child_process').execFile;
9+
10+
var values = [
11+
'hello world',
12+
null,
13+
undefined,
14+
false,
15+
0,
16+
{},
17+
{ key: 'value' },
18+
Symbol('I am a symbol'),
19+
function ok() {},
20+
['array', 'with', 4, 'values'],
21+
new Error('boo')
22+
];
23+
24+
{
25+
// Test that the resolution value is passed as second argument to callback
26+
values.forEach(function(value) {
27+
// Test and `async function`
28+
async function asyncFn() {
29+
return value;
30+
}
31+
32+
var cbAsyncFn = callbackify(asyncFn);
33+
cbAsyncFn(common.mustCall(function(err, ret) {
34+
assert.ifError(err);
35+
assert.strictEqual(ret, value);
36+
}));
37+
});
38+
}
39+
40+
{
41+
// Test that rejection reason is passed as first argument to callback
42+
values.forEach(function(value) {
43+
// Test an `async function`
44+
async function asyncFn() {
45+
return Promise.reject(value);
46+
}
47+
48+
var cbAsyncFn = callbackify(asyncFn);
49+
cbAsyncFn(common.mustCall(function (err, ret) {
50+
assert.strictEqual(ret, undefined);
51+
if (err instanceof Error) {
52+
if ('reason' in err) {
53+
assert(!value);
54+
assert.strictEqual(err.message, 'Promise was rejected with a falsy value');
55+
assert.strictEqual(err.reason, value);
56+
} else {
57+
assert.strictEqual(String(value).endsWith(err.message), true);
58+
}
59+
} else {
60+
assert.strictEqual(err, value);
61+
}
62+
}));
63+
});
64+
}
65+
66+
{
67+
// Test that arguments passed to callbackified function are passed to original
68+
values.forEach(function(value) {
69+
async function asyncFn(arg) {
70+
assert.strictEqual(arg, value);
71+
return arg;
72+
}
73+
74+
var cbAsyncFn = callbackify(asyncFn);
75+
cbAsyncFn(value, common.mustCall(function(err, ret) {
76+
assert.ifError(err);
77+
assert.strictEqual(ret, value);
78+
}));
79+
});
80+
}
81+
82+
{
83+
// Test that `this` binding is the same for callbackified and original
84+
values.forEach(function(value) {
85+
var iAmThat = {
86+
async fn(arg) {
87+
assert.strictEqual(this, iAmThat);
88+
return arg;
89+
},
90+
};
91+
iAmThat.cbFn = callbackify(iAmThat.fn);
92+
iAmThat.cbFn(value, common.mustCall(function(err, ret) {
93+
assert.ifError(err);
94+
assert.strictEqual(ret, value);
95+
assert.strictEqual(this, iAmThat);
96+
}));
97+
});
98+
}
99+
100+
{
101+
async function asyncFn() {
102+
return 42;
103+
}
104+
105+
var cb = callbackify(asyncFn);
106+
var args = [];
107+
108+
// Verify that the last argument to the callbackified function is a function.
109+
['foo', null, undefined, false, 0, {}, Symbol(), []].forEach(function(value) {
110+
args.push(value);
111+
common.expectsError(function() {
112+
cb(...args);
113+
}, {
114+
code: 'ERR_INVALID_ARG_TYPE',
115+
type: TypeError,
116+
message: 'The last argument must be of type Function'
117+
});
118+
});
119+
}
120+

test/node/callbackify.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
'use strict';
2+
3+
// This test checks that the semantics of `util.callbackify` are as described in
4+
// the API docs
5+
6+
var common = require('./common');
7+
var assert = require('assert');
8+
var callbackify = require('../../').callbackify;
9+
var execFile = require('child_process').execFile;
10+
11+
var values = [
12+
'hello world',
13+
null,
14+
undefined,
15+
false,
16+
0,
17+
{},
18+
{ key: 'value' },
19+
function ok() {},
20+
['array', 'with', 4, 'values'],
21+
new Error('boo')
22+
];
23+
if (typeof Symbol !== 'undefined') {
24+
values.push(Symbol('I am a symbol'));
25+
}
26+
27+
{
28+
// Test that the resolution value is passed as second argument to callback
29+
values.forEach(function(value) {
30+
// Test Promise factory
31+
function promiseFn() {
32+
return Promise.resolve(value);
33+
}
34+
35+
var cbPromiseFn = callbackify(promiseFn);
36+
cbPromiseFn(common.mustCall(function(err, ret) {
37+
assert.ifError(err);
38+
assert.strictEqual(ret, value);
39+
}));
40+
41+
// Test Thenable
42+
function thenableFn() {
43+
return {
44+
then: function(onRes, onRej) {
45+
onRes(value);
46+
}
47+
};
48+
}
49+
50+
var cbThenableFn = callbackify(thenableFn);
51+
cbThenableFn(common.mustCall(function(err, ret) {
52+
assert.ifError(err);
53+
assert.strictEqual(ret, value);
54+
}));
55+
});
56+
}
57+
58+
{
59+
// Test that rejection reason is passed as first argument to callback
60+
values.forEach(function(value) {
61+
// test a Promise factory
62+
function promiseFn() {
63+
return Promise.reject(value);
64+
}
65+
66+
var cbPromiseFn = callbackify(promiseFn);
67+
cbPromiseFn(common.mustCall(function(err, ret) {
68+
assert.strictEqual(ret, undefined);
69+
if (err instanceof Error) {
70+
if ('reason' in err) {
71+
assert(!value);
72+
assert.strictEqual(err.message, 'Promise was rejected with a falsy value');
73+
assert.strictEqual(err.reason, value);
74+
} else {
75+
assert.strictEqual(String(value).endsWith(err.message), true);
76+
}
77+
} else {
78+
assert.strictEqual(err, value);
79+
}
80+
}));
81+
82+
// Test Thenable
83+
function thenableFn() {
84+
return {
85+
then: function (onRes, onRej) {
86+
onRej(value);
87+
}
88+
};
89+
}
90+
91+
var cbThenableFn = callbackify(thenableFn);
92+
cbThenableFn(common.mustCall(function(err, ret) {
93+
assert.strictEqual(ret, undefined);
94+
if (err instanceof Error) {
95+
if ('reason' in err) {
96+
assert(!value);
97+
assert.strictEqual(err.message, 'Promise was rejected with a falsy value');
98+
assert.strictEqual(err.reason, value);
99+
} else {
100+
assert.strictEqual(String(value).endsWith(err.message), true);
101+
}
102+
} else {
103+
assert.strictEqual(err, value);
104+
}
105+
}));
106+
});
107+
}
108+
109+
{
110+
// Test that arguments passed to callbackified function are passed to original
111+
values.forEach(function(value) {
112+
function promiseFn(arg) {
113+
assert.strictEqual(arg, value);
114+
return Promise.resolve(arg);
115+
}
116+
117+
var cbPromiseFn = callbackify(promiseFn);
118+
cbPromiseFn(value, common.mustCall(function(err, ret) {
119+
assert.ifError(err);
120+
assert.strictEqual(ret, value);
121+
}));
122+
});
123+
}
124+
125+
{
126+
// Test that `this` binding is the same for callbackified and original
127+
values.forEach(function(value) {
128+
var iAmThis = {
129+
fn: function(arg) {
130+
assert.strictEqual(this, iAmThis);
131+
return Promise.resolve(arg);
132+
},
133+
};
134+
iAmThis.cbFn = callbackify(iAmThis.fn);
135+
iAmThis.cbFn(value, common.mustCall(function(err, ret) {
136+
assert.ifError(err);
137+
assert.strictEqual(ret, value);
138+
assert.strictEqual(this, iAmThis);
139+
}));
140+
});
141+
}
142+
143+
// These tests are not necessary in the browser.
144+
if (false) {
145+
// Test that callback that throws emits an `uncaughtException` event
146+
var fixture = fixtures.path('uncaught-exceptions', 'callbackify1.js');
147+
execFile(
148+
process.execPath,
149+
[fixture],
150+
common.mustCall(function (err, stdout, stderr) {
151+
assert.strictEqual(err.code, 1);
152+
assert.strictEqual(Object.getPrototypeOf(err).name, 'Error');
153+
assert.strictEqual(stdout, '');
154+
var errLines = stderr.trim().split(/[\r\n]+/);
155+
var errLine = errLines.find(function (l) { return /^Error/.exec(l) });
156+
assert.strictEqual(errLine, 'Error: ' + fixture);
157+
})
158+
);
159+
}
160+
161+
if (false) {
162+
// Test that handled `uncaughtException` works and passes rejection reason
163+
var fixture = fixtures.path('uncaught-exceptions', 'callbackify2.js');
164+
execFile(
165+
process.execPath,
166+
[fixture],
167+
common.mustCall(function (err, stdout, stderr) {
168+
assert.ifError(err);
169+
assert.strictEqual(stdout.trim(), fixture);
170+
assert.strictEqual(stderr, '');
171+
})
172+
);
173+
}
174+
175+
{
176+
// Verify that non-function inputs throw.
177+
['foo', null, undefined, false, 0, {}, typeof Symbol !== 'undefined' ? Symbol() : undefined, []].forEach(function(value) {
178+
common.expectsError(function() {
179+
callbackify(value);
180+
}, {
181+
code: 'ERR_INVALID_ARG_TYPE',
182+
type: TypeError,
183+
message: 'The "original" argument must be of type Function'
184+
});
185+
});
186+
}
187+
188+
if (require('is-async-supported')()) {
189+
require('./callbackify-async');
190+
}

test/node/common.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
var assert = require('assert');
2+
3+
var mustCalls = [];
4+
var common = {
5+
expectsError: function (fn, props) {
6+
try { fn(); }
7+
catch (err) {
8+
if (props.type) assert.equal(err.constructor, props.type);
9+
if (props.message) assert.equal(err.message, props.message);
10+
return;
11+
}
12+
assert.fail('expected error');
13+
},
14+
mustCall: function (fn) {
15+
function mustCall() {
16+
mustCall.called = true
17+
return fn.apply(this, arguments);
18+
}
19+
20+
mustCalls.push(mustCall);
21+
return mustCall;
22+
}
23+
};
24+
25+
process.on('exit', function () {
26+
mustCalls.forEach(function (mc) {
27+
assert(mc.called);
28+
});
29+
});
30+
31+
module.exports = common;

test/node/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ series([
1616
test(require.resolve('./format')),
1717
test(require.resolve('./inspect')),
1818
test(require.resolve('./log')),
19-
test(require.resolve('./promisify'))
19+
test(require.resolve('./promisify')),
20+
test(require.resolve('./callbackify'))
2021
]);

0 commit comments

Comments
 (0)