Created
June 30, 2023 11:21
-
-
Save petsel/af63d7f4280d07feb80d6041ab795662 to your computer and use it in GitHub Desktop.
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
/** | |
* see ... [https://stackoverflow.com/a/76575046/2627243] | |
* | |
* ... answering following SO question ... | |
* | |
* "How to prevent an async function from being invoked | |
* a 2nd time before having finished fetching the first | |
* query from a firestore database?" | |
*/ | |
(function (Object, Function, Symbol, Reflect) { | |
function getInternalTypeSignature(value) { | |
return Object.prototype.toString.call(value).trim(); | |
} | |
function getFunctionSignature(value) { | |
return Function.prototype.toString.call(value).trim(); | |
} | |
function getFunctionName(value) { | |
return Reflect.getOwnPropertyDescriptor(value, 'name').value; | |
} | |
function getSanitizedTarget(target) { | |
return target ?? null; | |
} | |
function isFunction(value) { | |
return ( | |
'function' === typeof value && | |
'function' === typeof value.call && | |
'function' === typeof value.apply | |
); | |
} | |
function isAsynFunction(value) { | |
return (getInternalTypeSignature(value) === '[object AsyncFunction]'); | |
} | |
function isArrowType(value) { | |
return ( | |
isFunction(value) && | |
/^(?:async\s*)?(?:\(.*?\)|[^(),=]+)\s*=>/.test(getFunctionSignature(value)) | |
); | |
} | |
// function toTitleCase(value) { | |
// return value.replace(/^\p{Ll}(?=\p{L}+)/gu, match => match.toUpperCase()); | |
// } | |
function createGatedAsyncFunctionExpression(fctName, proceed, sanitizedTarget) { | |
let isClosedGate = false; | |
return ({ | |
[ fctName ]: async function (...args) { | |
let result = Function.ClOSED_ASYNC_INVOCATION_GATE; | |
if (!isClosedGate) { | |
isClosedGate = true; | |
/** | |
* - an async gated function's `target` can be delegated at this function's | |
* invocation time, but only if it was not provided already at creation time. | |
*/ | |
const context = sanitizedTarget ?? getSanitizedTarget(this); | |
result = await proceed.apply(context, args); | |
isClosedGate = false; | |
} | |
return result; | |
}, | |
})[fctName]; | |
} | |
function createGatedAsyncArrowExpression(fctName, proceed) { | |
let isClosedGate = false; | |
return ({ | |
[ fctName ]: async (...args) => { | |
let result = Function.ClOSED_ASYNC_INVOCATION_GATE; | |
if (!isClosedGate) { | |
isClosedGate = true; | |
result = await proceed(...args); | |
isClosedGate = false; | |
} | |
return result; | |
}, | |
})[fctName]; | |
} | |
function createGatedAsyncFunction(proceed, target) { | |
// guard. | |
if (!isAsynFunction(proceed)) { | |
return proceed; | |
} | |
const isAsyncArrow = isArrowType(proceed); | |
const modifiedName = `gated ${ getFunctionName(proceed) }`; | |
// const modifiedName = `gated${ toTitleCase( getFunctionName(proceed) ) }`; | |
return isAsyncArrow | |
&& createGatedAsyncArrowExpression(modifiedName, proceed) | |
|| createGatedAsyncFunctionExpression(modifiedName, proceed, getSanitizedTarget(target)); | |
} | |
function toGate(target) { | |
return createGatedAsyncFunction(this, target); | |
} | |
const closedAsyncInvocationValue = Symbol('ClOSED_ASYNC_INVOCATION_GATE'); | |
const AsyncFunction = (async function() {}).constructor; | |
Reflect | |
.defineProperty( | |
Function, | |
'ClOSED_ASYNC_INVOCATION_GATE', { | |
get: () => closedAsyncInvocationValue, | |
}, | |
); | |
Reflect | |
.defineProperty( | |
Function, | |
'toAsyncGate', { | |
value: createGatedAsyncFunction, | |
}, | |
); | |
Reflect | |
.defineProperty( | |
AsyncFunction.prototype, | |
'gate', { | |
value: toGate, | |
} | |
); | |
return createGatedAsyncFunction; | |
}(Object, Function, Symbol, Reflect)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment