Skip to content

Commit

Permalink
Merge pull request #25 from defunctzombie/promisify-callbackify
Browse files Browse the repository at this point in the history
Add util.promisify and util.callbackify
  • Loading branch information
goto-bus-stop authored Jun 8, 2018
2 parents 0fe5860 + aafbe16 commit 2c65d29
Show file tree
Hide file tree
Showing 10 changed files with 1,039 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"license": "MIT",
"devDependencies": {
"airtap": "0.0.6",
"is-async-supported": "~1.2.0",
"run-series": "~1.1.4",
"tape": "~4.9.0"
},
Expand Down
169 changes: 169 additions & 0 deletions test/browser/callbackify.js
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();
});
2 changes: 2 additions & 0 deletions test/browser/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
require('./inspect');
require('./is');
require('./promisify');
require('./callbackify');
205 changes: 205 additions & 0 deletions test/browser/promisify.js
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();
});
Loading

0 comments on commit 2c65d29

Please sign in to comment.