-
-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #25 from defunctzombie/promisify-callbackify
Add util.promisify and util.callbackify
- Loading branch information
Showing
10 changed files
with
1,039 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
'use strict'; | ||
|
||
var test = require('tape'); | ||
var callbackify = require('../../').callbackify; | ||
|
||
if (typeof Promise === 'undefined') { | ||
console.log('no global Promise found, skipping callbackify tests'); | ||
return; | ||
} | ||
|
||
function after (n, cb) { | ||
var i = 0; | ||
return function () { | ||
if (++i === n) cb(); | ||
} | ||
} | ||
|
||
var values = [ | ||
'hello world', | ||
null, | ||
undefined, | ||
false, | ||
0, | ||
{}, | ||
{ key: 'value' }, | ||
function ok() {}, | ||
['array', 'with', 4, 'values'], | ||
new Error('boo') | ||
]; | ||
if (typeof Symbol !== 'undefined') { | ||
values.push(Symbol('I am a symbol')); | ||
} | ||
|
||
test('util.callbackify resolution value is passed as second argument to callback', function (t) { | ||
var end = after(values.length * 2, t.end); | ||
// Test that the resolution value is passed as second argument to callback | ||
values.forEach(function(value) { | ||
// Test Promise factory | ||
function promiseFn() { | ||
return Promise.resolve(value); | ||
} | ||
|
||
var cbPromiseFn = callbackify(promiseFn); | ||
cbPromiseFn(function(err, ret) { | ||
t.ifError(err); | ||
t.strictEqual(ret, value, 'cb ' + typeof value); | ||
end(); | ||
}); | ||
|
||
// Test Thenable | ||
function thenableFn() { | ||
return { | ||
then: function(onRes, onRej) { | ||
onRes(value); | ||
} | ||
}; | ||
} | ||
|
||
var cbThenableFn = callbackify(thenableFn); | ||
cbThenableFn(function(err, ret) { | ||
t.ifError(err); | ||
t.strictEqual(ret, value, 'thenable ' + typeof value); | ||
end(); | ||
}); | ||
}); | ||
}); | ||
|
||
test('util.callbackify rejection reason is passed as first argument to callback', function (t) { | ||
var end = after(values.length * 2, t.end); | ||
// Test that rejection reason is passed as first argument to callback | ||
values.forEach(function(value) { | ||
// test a Promise factory | ||
function promiseFn() { | ||
return Promise.reject(value); | ||
} | ||
|
||
var cbPromiseFn = callbackify(promiseFn); | ||
cbPromiseFn(function(err, ret) { | ||
t.strictEqual(ret, undefined, 'cb ' + typeof value); | ||
if (err instanceof Error) { | ||
if ('reason' in err) { | ||
t.ok(!value); | ||
t.strictEqual(err.message, 'Promise was rejected with a falsy value'); | ||
t.strictEqual(err.reason, value); | ||
} else { | ||
t.strictEqual(String(value).slice(-err.message.length), err.message); | ||
} | ||
} else { | ||
t.strictEqual(err, value); | ||
} | ||
end(); | ||
}); | ||
|
||
// Test Thenable | ||
function thenableFn() { | ||
return { | ||
then: function (onRes, onRej) { | ||
onRej(value); | ||
} | ||
}; | ||
} | ||
|
||
var cbThenableFn = callbackify(thenableFn); | ||
cbThenableFn(function(err, ret) { | ||
t.strictEqual(ret, undefined, 'thenable ' + typeof value); | ||
if (err instanceof Error) { | ||
if ('reason' in err) { | ||
t.ok(!value); | ||
t.strictEqual(err.message, 'Promise was rejected with a falsy value'); | ||
t.strictEqual(err.reason, value); | ||
} else { | ||
t.strictEqual(String(value).slice(-err.message.length), err.message); | ||
} | ||
} else { | ||
t.strictEqual(err, value); | ||
} | ||
end(); | ||
}); | ||
}); | ||
}); | ||
|
||
test('util.callbackify arguments passed to callbackified function are passed to original', function (t) { | ||
var end = after(values.length, t.end); | ||
// Test that arguments passed to callbackified function are passed to original | ||
values.forEach(function(value) { | ||
function promiseFn(arg) { | ||
t.strictEqual(arg, value); | ||
return Promise.resolve(arg); | ||
} | ||
|
||
var cbPromiseFn = callbackify(promiseFn); | ||
cbPromiseFn(value, function(err, ret) { | ||
t.ifError(err); | ||
t.strictEqual(ret, value); | ||
end(); | ||
}); | ||
}); | ||
}); | ||
|
||
test('util.callbackify `this` binding is the same for callbackified and original', function (t) { | ||
var end = after(values.length, t.end); | ||
// Test that `this` binding is the same for callbackified and original | ||
values.forEach(function(value) { | ||
var iAmThis = { | ||
fn: function(arg) { | ||
t.strictEqual(this, iAmThis); | ||
return Promise.resolve(arg); | ||
}, | ||
}; | ||
iAmThis.cbFn = callbackify(iAmThis.fn); | ||
iAmThis.cbFn(value, function(err, ret) { | ||
t.ifError(err); | ||
t.strictEqual(ret, value); | ||
t.strictEqual(this, iAmThis); | ||
end(); | ||
}); | ||
}); | ||
}); | ||
|
||
test('util.callbackify non-function inputs throw', function (t) { | ||
// Verify that non-function inputs throw. | ||
['foo', null, undefined, false, 0, {}, typeof Symbol !== 'undefined' ? Symbol() : undefined, []].forEach(function(value) { | ||
t.throws( | ||
function() { callbackify(value); }, | ||
'The "original" argument must be of type Function' | ||
); | ||
}); | ||
t.end(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
require('./inspect'); | ||
require('./is'); | ||
require('./promisify'); | ||
require('./callbackify'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
var promisify = require('../../').promisify; | ||
var test = require('tape'); | ||
|
||
var hasSymbol = typeof Symbol !== 'undefined'; | ||
|
||
if (typeof Promise === 'undefined') { | ||
console.log('no global Promise found, skipping promisify tests'); | ||
return; | ||
} | ||
|
||
var callbacker = function (arg, cb) { | ||
setTimeout(function () { | ||
if (typeof arg === 'string') | ||
cb(null, arg.toUpperCase()); | ||
else cb(new TypeError('incorrect type')); | ||
}, 5); | ||
} | ||
var promiser = promisify(callbacker); | ||
|
||
test('util.promisify resolves', function (t) { | ||
var promise = promiser(__filename); | ||
t.ok(promise instanceof Promise); | ||
promise.then(function (value) { | ||
t.deepEqual(value, __filename.toUpperCase()); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify rejects', function (t) { | ||
var promise = promiser(42); | ||
promise.catch(function (error) { | ||
t.equal(error.message, 'incorrect type'); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify custom', { skip: !hasSymbol }, function (t) { | ||
function fn() {} | ||
function promisifedFn() {} | ||
fn[promisify.custom] = promisifedFn; | ||
t.strictEqual(promisify(fn), promisifedFn); | ||
t.strictEqual(promisify(promisify(fn)), promisifedFn); | ||
t.end(); | ||
}); | ||
|
||
test('util.promisify custom of invalid type', { skip: !hasSymbol }, function (t) { | ||
function fn2() {} | ||
fn2[promisify.custom] = 42; | ||
t.throws( | ||
function () { promisify(fn2); }, | ||
/must be of type Function/ | ||
); | ||
t.end(); | ||
}); | ||
|
||
test('util.promisify multiple callback values', function (t) { | ||
function fn5(callback) { | ||
callback(null, 'foo', 'bar'); | ||
} | ||
promisify(fn5)().then(function (value) { | ||
t.strictEqual(value, 'foo'); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify no callback success value', function (t) { | ||
function fn6(callback) { | ||
callback(null); | ||
} | ||
promisify(fn6)().then(function (value) { | ||
t.strictEqual(value, undefined); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify no callback arguments at all', function (t) { | ||
function fn7(callback) { | ||
callback(); | ||
} | ||
promisify(fn7)().then(function (value) { | ||
t.strictEqual(value, undefined); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify passing arguments', function (t) { | ||
function fn8(err, val, callback) { | ||
callback(err, val); | ||
} | ||
promisify(fn8)(null, 42).then(function (value) { | ||
t.strictEqual(value, 42); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify passing arguments (rejects)', function (t) { | ||
function fn9(err, val, callback) { | ||
callback(err, val); | ||
} | ||
promisify(fn9)(new Error('oops'), null).catch(function (err) { | ||
t.strictEqual(err.message, 'oops'); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify chain', function (t) { | ||
function fn9(err, val, callback) { | ||
callback(err, val); | ||
} | ||
|
||
|
||
Promise.resolve() | ||
.then(function () { return promisify(fn9)(null, 42); }) | ||
.then(function (value) { | ||
t.strictEqual(value, 42); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify keeps correct `this`', function (t) { | ||
var o = {}; | ||
var fn10 = promisify(function(cb) { | ||
|
||
cb(null, this === o); | ||
}); | ||
|
||
o.fn = fn10; | ||
|
||
o.fn().then(function(val) { | ||
t.ok(val); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify calling callback multiple times', function (t) { | ||
var err = new Error('Should not have called the callback with the error.'); | ||
var stack = err.stack; | ||
|
||
var fn11 = promisify(function(cb) { | ||
cb(null); | ||
cb(err); | ||
}); | ||
|
||
Promise.resolve() | ||
.then(function () { return fn11(); }) | ||
.then(function () { return Promise.resolve(); }) | ||
.then(function () { | ||
t.strictEqual(stack, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
// Can't do this without Symbol() unfortunately | ||
test('util.promisify promisifying a promisified function', { skip: !hasSymbol }, function (t) { | ||
function c() { } | ||
var a = promisify(function() { }); | ||
var b = promisify(a); | ||
t.notStrictEqual(c, a); | ||
t.strictEqual(a, b); | ||
t.end(); | ||
}); | ||
|
||
test('util.promisify sync throw becomes rejection', function (t) { | ||
var errToThrow; | ||
var thrower = promisify(function(a, b, c, cb) { | ||
errToThrow = new Error(); | ||
throw errToThrow; | ||
}); | ||
thrower(1, 2, 3) | ||
.then(t.fail) | ||
.then(t.fail, function (e) { | ||
t.strictEqual(e, errToThrow); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify callback and sync throw', function (t) { | ||
var err = new Error(); | ||
|
||
var a = promisify(function (cb) { cb(err) })(); | ||
var b = promisify(function () { throw err; })(); | ||
|
||
Promise.all([ | ||
a.then(t.fail, function(e) { | ||
t.strictEqual(err, e); | ||
}), | ||
b.then(t.fail, function(e) { | ||
t.strictEqual(err, e); | ||
}) | ||
]).then(function () { | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('util.promisify throws for incorrect argument types', function (t) { | ||
var array = [undefined, null, true, 0, 'str', {}, []]; | ||
if (typeof Symbol !== 'undefined') array.push(Symbol()); | ||
array.forEach(function (input) { | ||
t.throws( | ||
function () { promisify(input); }, | ||
'The "original" argument must be of type Function' | ||
); | ||
}); | ||
t.end(); | ||
}); |
Oops, something went wrong.