Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add util.promisify and util.callbackify #25

Merged
merged 4 commits into from
Jun 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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