Skip to content

Commit

Permalink
Merge 35b151c into a06338e
Browse files Browse the repository at this point in the history
  • Loading branch information
janpaepke committed Dec 1, 2022
2 parents a06338e + 35b151c commit d3b68c8
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 32 deletions.
16 changes: 0 additions & 16 deletions source/checkThenable.js

This file was deleted.

28 changes: 12 additions & 16 deletions source/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import checkThenable from './checkThenable';
import takeThen from './takeThen';

// Steal the splice function from this empty array.
const { splice } = [];
Expand All @@ -20,9 +20,10 @@ function build(name, logic) {
const context = this,
forwardingArguments = arguments;
var result;
// If the value is thenable, recall this function (recursively) once it resolves.
if (checkThenable(value)) {
return value.then(value => {
// If the value is promise-like, recall this function (recursively) once it resolves.
const then = takeThen(value);
if (then) {
return then(value => {
// const [, ...callbacks] = arguments;
// return implementation.call(context, value, ...callbacks);
// ↓
Expand Down Expand Up @@ -134,17 +135,12 @@ export const run =
apply =
build(
'apply',
function applyLogic(value, callback, result) {
if (
// Call the callback. If the result is thenable, chain a function to it which will return the value, and return
// that chain.
checkThenable(
result = callback.call(this, value)
)
) {
return result.then(() => value);
}
// If the result is not thenable, return the value.
return value;
function applyLogic(value, callback, /* This is never provided, thus initially undefined → */ then) {
// Call the callback and check whether the result is promise-like.
return (then = takeThen(callback.call(this, value)))
// If the result is promise-like, chain a function to it which will return the value, then return that chain.
? then(() => value)
// If the result is not promise-like, return the value.
: value;
}
);
24 changes: 24 additions & 0 deletions source/takeThen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Returns the `then` property of the passed input, bound to the passed input, if said passed input is promise-like.
* Returns a falsy value (specifically `undefined` or `false`) otherwise.
*
* An input is considered promise-like if it has a `then` property which can be retrieved and is a function.
*/
export default function takeThen(input, /* This is never provided, thus initially undefined → */ then) {
// Return undefined if the input is null-ish. The try-catch below would cause undefined to be returned for those
// inputs anyway, making this if block technically redundant. However, it does speed up this function significantly
// for those inputs. See https://github.com/Pimm/ruply/issues/2.
if (null == input) {
return /* undefined */;
}
// Return undefined if retrieving the then property causes an error to be thrown. Since the promise awareness is
// somewhat of a hidden feature of this library, it should operate as unintrusive as possible.
try {
({ then } = input);
} catch (error) {
return /* undefined */;
}
// return 'function' == typeof then ? then.bind(input) : false;
// ↓
return 'function' == typeof then && then.bind(input);
}
69 changes: 69 additions & 0 deletions test/throwing-inputs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @jest-environment node
*/

import { run, runIf, apply } from '..';

function createCallback() {
return jest.fn(() => 'result');
}

const hostileObject = {
get then() {
throw 'error';
}
};

const hostileProxy = new Proxy({}, {
get(_, name) {
if ('then' == name) {
throw 'error';
}
}
});

test('throwing-inputs', () => {
expect.assertions(12);
// run with hostile object.
run(createCallback(), callback => {
expect(
() => run(hostileObject, callback)
).not.toThrow();
expect(callback).toHaveBeenCalledWith(hostileObject);
});
// runIf with hostile object.
run(createCallback(), callback => {
expect(
() => runIf(hostileObject, callback)
).not.toThrow();
expect(callback).toHaveBeenCalledWith(hostileObject);
});
// apply with hostile object.
run(createCallback(), callback => {
expect(
() => apply(hostileObject, callback)
).not.toThrow();
expect(callback).toHaveBeenCalledWith(hostileObject);
});
// run with hostile proxy.
run(createCallback(), callback => {
expect(
() => run(hostileProxy, callback)
).not.toThrow();
expect(callback).toHaveBeenCalledWith(hostileProxy);
});
// runIf with hostile proxy.
run(createCallback(), callback => {
expect(
() => runIf(hostileProxy, callback)
).not.toThrow();
expect(callback).toHaveBeenCalledWith(hostileProxy);
});
// apply with hostile proxy.
run(createCallback(), callback => {
expect(
() => apply(hostileProxy, callback)
).not.toThrow();
expect(callback).toHaveBeenCalledWith(hostileProxy);
});
});

0 comments on commit d3b68c8

Please sign in to comment.