Skip to content

Instantly share code, notes, and snippets.

@petsel
Created June 30, 2023 11:21
Show Gist options
  • Save petsel/af63d7f4280d07feb80d6041ab795662 to your computer and use it in GitHub Desktop.
Save petsel/af63d7f4280d07feb80d6041ab795662 to your computer and use it in GitHub Desktop.
/**
* 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