-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: extract thenify and executeWithThenable (#229)
- Loading branch information
Showing
5 changed files
with
298 additions
and
45 deletions.
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,28 @@ | ||
'use strict' | ||
const { isPromiseLike } = require('./is-promise-like') | ||
const { kAvvio } = require('./symbols') | ||
|
||
/** | ||
* @callback ExecuteWithThenableCallback | ||
* @param {Error} error | ||
* @returns {void} | ||
*/ | ||
|
||
/** | ||
* @param {Function} func | ||
* @param {Array<any>} args | ||
* @param {ExecuteWithThenableCallback} [callback] | ||
*/ | ||
function executeWithThenable (func, args, callback) { | ||
const result = func.apply(func, args) | ||
if (isPromiseLike(result) && !result[kAvvio]) { | ||
// process promise but not avvio mock thenable | ||
result.then(() => process.nextTick(callback), (error) => process.nextTick(callback, error)) | ||
} else if (callback) { | ||
process.nextTick(callback) | ||
} | ||
} | ||
|
||
module.exports = { | ||
executeWithThenable | ||
} |
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,60 @@ | ||
'use strict' | ||
|
||
const { debug } = require('./debug') | ||
const { kThenifyDoNotWrap } = require('./symbols') | ||
|
||
/** | ||
* @callback PromiseConstructorLikeResolve | ||
* @param {any} value | ||
* @returns {void} | ||
*/ | ||
|
||
/** | ||
* @callback PromiseConstructorLikeReject | ||
* @param {reason} error | ||
* @returns {void} | ||
*/ | ||
|
||
/** | ||
* @callback PromiseConstructorLike | ||
* @param {PromiseConstructorLikeResolve} resolve | ||
* @param {PromiseConstructorLikeReject} reject | ||
* @returns {void} | ||
*/ | ||
|
||
/** | ||
* @returns {PromiseConstructorLike} | ||
*/ | ||
function thenify () { | ||
// If the instance is ready, then there is | ||
// nothing to await. This is true during | ||
// await server.ready() as ready() resolves | ||
// with the server, end we will end up here | ||
// because of automatic promise chaining. | ||
if (this.booted) { | ||
debug('thenify returning undefined because we are already booted') | ||
return | ||
} | ||
|
||
// Calling resolve(this._server) would fetch the then | ||
// property on the server, which will lead it here. | ||
// If we do not break the recursion, we will loop | ||
// forever. | ||
if (this[kThenifyDoNotWrap]) { | ||
this[kThenifyDoNotWrap] = false | ||
return | ||
} | ||
|
||
debug('thenify') | ||
return (resolve, reject) => { | ||
const p = this._loadRegistered() | ||
return p.then(() => { | ||
this[kThenifyDoNotWrap] = true | ||
return resolve(this._server) | ||
}, reject) | ||
} | ||
} | ||
|
||
module.exports = { | ||
thenify | ||
} |
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,82 @@ | ||
'use strict' | ||
|
||
const { test } = require('tap') | ||
const { executeWithThenable } = require('../../lib/executeWithThenable') | ||
const { kAvvio } = require('../../lib/symbols') | ||
|
||
test('executeWithThenable', (t) => { | ||
t.plan(6) | ||
|
||
t.test('passes the arguments to the function', (t) => { | ||
t.plan(5) | ||
|
||
executeWithThenable((...args) => { | ||
t.equal(args.length, 3) | ||
t.equal(args[0], 1) | ||
t.equal(args[1], 2) | ||
t.equal(args[2], 3) | ||
}, [1, 2, 3], (err) => { | ||
t.error(err) | ||
}) | ||
}) | ||
|
||
t.test('function references this to itself', (t) => { | ||
t.plan(2) | ||
|
||
const func = function () { | ||
t.equal(this, func) | ||
} | ||
executeWithThenable(func, [], (err) => { | ||
t.error(err) | ||
}) | ||
}) | ||
|
||
t.test('handle resolving Promise of func', (t) => { | ||
t.plan(1) | ||
|
||
const fn = function () { | ||
return Promise.resolve(42) | ||
} | ||
|
||
executeWithThenable(fn, [], (err) => { | ||
t.error(err) | ||
}) | ||
}) | ||
|
||
t.test('handle rejecting Promise of func', (t) => { | ||
t.plan(1) | ||
|
||
const fn = function () { | ||
return Promise.reject(new Error('Arbitrary Error')) | ||
} | ||
|
||
executeWithThenable(fn, [], (err) => { | ||
t.equal(err.message, 'Arbitrary Error') | ||
}) | ||
}) | ||
|
||
t.test('dont handle avvio mocks PromiseLike results but use callback if provided', (t) => { | ||
t.plan(1) | ||
|
||
const fn = function () { | ||
const result = Promise.resolve(42) | ||
result[kAvvio] = true | ||
} | ||
|
||
executeWithThenable(fn, [], (err) => { | ||
t.error(err) | ||
}) | ||
}) | ||
|
||
t.test('dont handle avvio mocks Promises and if no callback is provided', (t) => { | ||
t.plan(1) | ||
|
||
const fn = function () { | ||
t.pass(1) | ||
const result = Promise.resolve(42) | ||
result[kAvvio] = true | ||
} | ||
|
||
executeWithThenable(fn, []) | ||
}) | ||
}) |
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,123 @@ | ||
'use strict' | ||
|
||
const { test, mock } = require('tap') | ||
const { kThenifyDoNotWrap } = require('../../lib/symbols') | ||
|
||
test('thenify', (t) => { | ||
t.plan(7) | ||
|
||
t.test('return undefined if booted', (t) => { | ||
t.plan(2) | ||
|
||
const { thenify } = mock('../../lib/thenify', { | ||
'../../lib/debug': { | ||
debug: (message) => { t.equal(message, 'thenify returning undefined because we are already booted') } | ||
} | ||
}) | ||
const result = thenify.call({ | ||
booted: true | ||
}) | ||
t.equal(result, undefined) | ||
}) | ||
|
||
t.test('return undefined if kThenifyDoNotWrap is true', (t) => { | ||
t.plan(1) | ||
|
||
const { thenify } = require('../../lib/thenify') | ||
const result = thenify.call({ | ||
[kThenifyDoNotWrap]: true | ||
}) | ||
t.equal(result, undefined) | ||
}) | ||
|
||
t.test('return PromiseConstructorLike if kThenifyDoNotWrap is false', (t) => { | ||
t.plan(3) | ||
|
||
const { thenify } = mock('../../lib/thenify', { | ||
'../../lib/debug': { | ||
debug: (message) => { t.equal(message, 'thenify') } | ||
} | ||
}) | ||
const promiseContructorLike = thenify.call({ | ||
[kThenifyDoNotWrap]: false | ||
}) | ||
|
||
t.type(promiseContructorLike, 'function') | ||
t.equal(promiseContructorLike.length, 2) | ||
}) | ||
|
||
t.test('return PromiseConstructorLike', (t) => { | ||
t.plan(3) | ||
|
||
const { thenify } = mock('../../lib/thenify', { | ||
'../../lib/debug': { | ||
debug: (message) => { t.equal(message, 'thenify') } | ||
} | ||
}) | ||
const promiseContructorLike = thenify.call({}) | ||
|
||
t.type(promiseContructorLike, 'function') | ||
t.equal(promiseContructorLike.length, 2) | ||
}) | ||
|
||
t.test('resolve should return _server', async (t) => { | ||
t.plan(1) | ||
|
||
const { thenify } = require('../../lib/thenify') | ||
|
||
const server = { | ||
_loadRegistered: () => { | ||
return Promise.resolve() | ||
}, | ||
_server: 'server' | ||
} | ||
const promiseContructorLike = thenify.call(server) | ||
|
||
promiseContructorLike(function (value) { | ||
t.equal(value, 'server') | ||
}, function (reason) { | ||
t.error(reason) | ||
}) | ||
}) | ||
|
||
t.test('resolving should set kThenifyDoNotWrap to true', async (t) => { | ||
t.plan(1) | ||
|
||
const { thenify } = require('../../lib/thenify') | ||
|
||
const server = { | ||
_loadRegistered: () => { | ||
return Promise.resolve() | ||
}, | ||
[kThenifyDoNotWrap]: false, | ||
_server: 'server' | ||
} | ||
const promiseContructorLike = thenify.call(server) | ||
|
||
promiseContructorLike(function (value) { | ||
t.equal(server[kThenifyDoNotWrap], true) | ||
}, function (reason) { | ||
t.error(reason) | ||
}) | ||
}) | ||
|
||
t.test('rejection should pass through to reject', async (t) => { | ||
t.plan(1) | ||
|
||
const { thenify } = require('../../lib/thenify') | ||
|
||
const server = { | ||
_loadRegistered: () => { | ||
return Promise.reject(new Error('Arbitrary rejection')) | ||
}, | ||
_server: 'server' | ||
} | ||
const promiseContructorLike = thenify.call(server) | ||
|
||
promiseContructorLike(function (value) { | ||
t.error(value) | ||
}, function (reason) { | ||
t.equal(reason.message, 'Arbitrary rejection') | ||
}) | ||
}) | ||
}) |