Last active
June 27, 2024 22:55
-
-
Save petsel/e73c59245bb97c0066472b92c0aa5bab 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
(function () { | |
// BEGIN :: module scope | |
'use strict'; | |
/* function type specifc detection helpers */ | |
/** | |
* Detects whether a passed function type features a truly `writable` | |
* `prototype` property. | |
* | |
* @param {Function} value | |
* Assumes a `'function'` type, but does not check for it. | |
* @returns {boolean} | |
* Returns whether the passed value features a truly `writable` | |
* `prototype` property. | |
*/ | |
function hasWritablePrototype(value) { | |
return Object.getOwnPropertyDescriptor(value, 'prototype')?.writable === true; | |
} | |
/** | |
* Reaches for any value's built-in type-signature by making use of ... | |
* | |
* ``` | |
* Object.prototype.toString.call(value); | |
* ``` | |
* | |
* ... which helps avoiding possibly | |
* manipulated `toString` behavior. | |
* | |
* @param {any} value | |
* The to be processed value. | |
* @returns {string} | |
* Returns the value's built-in type-signature | |
* like e.g. `'[object Date]'`. | |
*/ | |
function getBuiltInTypeSignature(value) { | |
return Object.prototype.toString.call(value).trim(); | |
} | |
/** | |
* Reaches for a function's true stringified version by making use of ... | |
* | |
* ``` | |
* Function.prototype.toString.call(value); | |
* ``` | |
* | |
* ... which helps avoiding possibly | |
* manipulated `toString` behavior. | |
* | |
* @param {Function} value | |
* Assumes a `'function'` type, but does not check for it. | |
* @returns {string} | |
* Returns a function's true/real stringified implementation. | |
*/ | |
function getFunctionSignature(value) { | |
return Function.prototype.toString.call(value).trim(); | |
} | |
/** | |
* Reaches for a function's direct/immediate constructor-function. | |
* | |
* @param {Function} value | |
* Assumes a `'function'` type, but does not check for it. | |
* @returns {string} | |
* Returns a function's direct/immediate constructor-function. | |
*/ | |
function getConstructorFunction(value) { | |
return Reflect.getOwnPropertyDescriptor( | |
Object.getPrototypeOf(value), 'constructor' | |
) | |
.value; | |
} | |
const AsyncFunction = getConstructorFunction(async function () {}); | |
// const GeneratorFunction = getConstructorFunction(function* () {}); | |
// const AsyncGeneratorFunction = getConstructorFunction(async function* () {}) | |
/** | |
* Detects any function type, which is ... | |
* | |
* ... the `typeof` operator not only returns the `'function'` string | |
* for the processed `value`, but the latter also features both of a | |
* function's call methods `call` and `apply`. | |
* | |
* @param {any} value | |
* The to be processed value. | |
* @returns {boolean} | |
* A boolean value which indicates whether the tested value is a function. | |
*/ | |
function isFunction(value) { | |
return ( | |
typeof value === 'function' && | |
typeof value.call === 'function' && | |
typeof value.apply === 'function' | |
); | |
} | |
/** | |
* Detects whether the passed `value` is any kind of arrow function, | |
* either async or not. | |
* | |
* @param {any} value | |
* The to be processed value. | |
* @returns {boolean} | |
* A boolean value which indicates whether the tested value is either | |
* kind of arrow function. | |
*/ | |
function isArrowFunctionType(value) { | |
return ( | |
isFunction(value) && | |
/^(?:async\s*)?(?:\(.*?\)|[^(),=]+)\s*=>/.test(getFunctionSignature(value)) | |
); | |
} | |
/** | |
* Detects whether the passed `value` is any kind of (non generator) async function, | |
* - either async arrow (expression) | |
* - or async function expression | |
* - or async function statement. | |
* | |
* @param {any} value | |
* The to be processed value. | |
* @returns {boolean} | |
* A boolean value which indicates whether the tested value is an async function. | |
*/ | |
function isAsyncFunction(value) { | |
return !!value && getBuiltInTypeSignature(value) === '[object AsyncFunction]'; | |
} | |
// /** | |
// * Detects whether the passed `value` is explicitly an | |
// * `AsyncGeneratorFunction` type. | |
// * | |
// * @param {any} value | |
// * The to be processed value. | |
// * @returns {boolean} | |
// * A boolean value which indicates whether the tested | |
// * value is explicitly an `AsyncGeneratorFunction` type. | |
// */ | |
// function isAsyncGeneratorFunction(value) { | |
// return !!value && getBuiltInTypeSignature(value) === '[object AsyncGeneratorFunction]'; | |
// } | |
// | |
// /** | |
// * Detects whether the passed `value` is explicitly a | |
// * `GeneratorFunction` type. | |
// * | |
// * @param {any} value | |
// * The to be processed value. | |
// * @returns {boolean} | |
// * A boolean value which indicates whether the | |
// * tested value is explicitly a `GeneratorFunction` type. | |
// */ | |
// function isGeneratorFunction(value) { | |
// return !!value && getBuiltInTypeSignature(value) === '[object GeneratorFunction]'; | |
// } | |
/** | |
* Detects whether the passed `value` is any kind of generator function, | |
* either async or not. | |
* | |
* @param {any} value | |
* The to be processed value. | |
* @returns {boolean} | |
* A boolean value which indicates whether the tested value is either | |
* kind of generator function. | |
*/ | |
function isGeneratorFunctionType(value) { | |
const signature = !!value && getBuiltInTypeSignature(value); | |
return signature && ( | |
signature === '[object GeneratorFunction]' || | |
signature === '[object AsyncGeneratorFunction]' | |
); | |
} | |
/** | |
* Detects whether the passed `value` is exclusively the | |
* only known function type as far back as with/at ES3 | |
* (in addition to all the built-in constructor functions). | |
* | |
* @param {any} value | |
* The to be processed value. | |
* @returns {boolean} | |
* A boolean value which indicates whether the tested value | |
* is exclusively the only known function type back at ES3. | |
*/ | |
function isES3Function(value) { | |
return ( | |
isFunction(value) && | |
hasWritablePrototype(value) && | |
!isGeneratorFunctionType(value) && | |
// - detects any instance of a class that extends `Function`. | |
!getFunctionSignature(getConstructorFunction(value)).startsWith('class ') | |
); | |
} | |
/* modifier specifc reflection and configuration helpers */ | |
/** | |
* Reaches for a function's own name. | |
* | |
* @param {Function} value | |
* Assumes a `'function'` type, but does not check for it. | |
* @returns {string} | |
* Returns a function's own name. | |
*/ | |
function getOwnName(value) { | |
return Reflect.getOwnPropertyDescriptor(value, 'name')?.value ?? ''; | |
} | |
/** | |
* A 'hide source' specific `toString` implementation | |
* which targets function types. | |
* | |
* - see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
* | |
* @this {Function} | |
* Any modifier-function that had been | |
* passed through `asConfiguredModifier`. | |
* | |
* @returns {string} | |
* A string value, a modifier-function's stringified version which shows | |
* the modifier-function's name but hides its implementation/source code. | |
*/ | |
function hideSource() { | |
return `function ${ getOwnName(this) }() { [hidden source] }`; | |
} | |
/** | |
* Assigns the above implemented 'hide source' specific `toString` | |
* behavior to any passed function type. | |
* | |
* @param {string} name | |
* The modifier name as it is supposed to be shown by the | |
* 'hide source' and modifier specific `toString` method, | |
* even within minified source code environments. | |
* @param {Function} modifier | |
* Assumes a modifier `'function'` type, but does not check for it. | |
* @returns {Function} | |
* Returns the passed and augmented modifier-function. | |
*/ | |
function asConfiguredModifier(name, modifier) { | |
Reflect.defineProperty(modifier, 'name', { | |
// - assures the correct fuction name for 'hide source' | |
// specific function stringification, even within | |
// minified source code environments. | |
...Reflect.getOwnPropertyDescriptor(modifier, 'name'), | |
value: name, | |
}); | |
Reflect.defineProperty(modifier, 'toString', { | |
configurable: true, value: hideSource, | |
}); | |
return modifier; | |
} | |
const modifierConfig = { writable: true, configurable: true }; | |
/** | |
* Assumes the passed function type to be a modified function and | |
* does augment it with modified function specific behavior/traits | |
* which are ... | |
* | |
* - a changed function `name` property | |
* with a modification specific prefix, | |
* - an additional `origin` property which | |
* refers to the original/unmodified function, | |
* - an additional `handler` property which refers | |
* to the modification specific handler function. | |
* | |
* @param {string} modifierName | |
* The specific modifier name which becomes the prefix part of the | |
* function's changed name. | |
* @param {Function} origin | |
* The original/unmodified function's reference. | |
* @param {Function} handler | |
* The reference of the modification specific handler function. | |
* @param {Function} modified | |
* The modified `'function'` type (assumed, but not checked for). | |
* @returns {Function} | |
* Returns the passed and augmented modified function. | |
*/ | |
function asConfiguredModification(modifierName, origin, handler, modified) { | |
const nameDescriptor = Reflect.getOwnPropertyDescriptor(origin, 'name'); | |
Reflect.defineProperty(modified, 'name', { | |
...nameDescriptor, | |
value: `modified::${ modifierName } ${ nameDescriptor.value }`, | |
}); | |
Reflect.defineProperty(modified, 'origin', { value: origin }); | |
Reflect.defineProperty(modified, 'handler', { value: handler }); | |
// Reflect.defineProperty(modified, 'origin', { get: () => origin }); | |
// Reflect.defineProperty(modified, 'handler', { get: () => handler }); | |
return modified; | |
} | |
/* all modifier specifc implementations */ | |
/* function type validation guards for any modifier specific implementation */ | |
function runThrowingBaseValidationGuard(modifierName, proceed, handler, isAsync = false) { | |
if (!isFunction(proceed)) { | |
throw new TypeError([ | |
'The value delegated to', | |
`\`${ isAsync && 'Async' || '' }Function.prototype.${ modifierName }\``, | |
'needs to be at least a `Function` instance.', | |
].join(' ')); | |
} | |
if (!isFunction(handler)) { | |
throw new TypeError([ | |
'The', `${ isAsync && 'asynchronous ' || '' }\`${ modifierName }\``, | |
'modifier\'s 1st `handler` parameter has to be at least a `Function` instance.', | |
].join(' ')); | |
} | |
} | |
function runThrowingAsyncValidationGuard(modifierName, proceed, handler) { | |
runThrowingBaseValidationGuard(modifierName, proceed, handler, true); | |
if ( | |
!isAsyncFunction(proceed) && | |
!isAsyncFunction(handler) | |
) { | |
throw new TypeError([ | |
'In case the value delegated to', `\`AsyncFunction.prototype.${ modifierName }\``, | |
'is a non asynchronous function type, the asynchronous', `\`${ modifierName }\``, | |
'modifier\'s 1st `handler` parameter then has to be exclusively an `AsyncFunction`', | |
'instance.', | |
].join(' ')); | |
} | |
if ( | |
isAsyncFunction(proceed) && | |
!isAsyncFunction(handler) && | |
!isArrowFunctionType(handler) && | |
!isES3Function(handler) | |
) { | |
throw new TypeError([ | |
'In case of modifying an asynchronous function type, the', `\`${ modifierName }\``, | |
'modifier\'s 1st `handler` parameter has to be either an instance of `AsyncFunction`', | |
'or an arrow function expression or an ES3 function type.', | |
].join(' ')); | |
} | |
} | |
/* +++ AROUND +++ and +++ ASYNC AROUND +++ */ | |
// - The modified function enables full control over the original function's | |
// control flow by providing access to everything, the original function, | |
// the custom implemented around handler itself, the call context and the | |
// passed arguments. Thus, the modified function's return value is determined | |
// exclusively by the around handler's implementation. | |
function aroundModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingBaseValidationGuard('around', proceed, handler); | |
target = target ?? null; | |
return ( | |
isAsyncFunction(proceed) || | |
isAsyncFunction(handler) | |
// delegate the modification to the async `around` implementation. | |
) && AsyncFunction.prototype.around.call(proceed, handler, target) || | |
asConfiguredModification( | |
'around', proceed, handler, | |
function /* aroundType */(...args) { | |
return handler.call((this ?? target), proceed, handler, ...args); | |
}, | |
); | |
} | |
function asyncAroundModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingAsyncValidationGuard('around', proceed, handler); | |
target = target ?? null; | |
return asConfiguredModification( | |
'around', proceed, handler, | |
async function /* asyncAroundType */(...args) { | |
return await handler.call((this ?? target), proceed, handler, ...args); | |
}, | |
); | |
} | |
Reflect.defineProperty(Function.prototype, 'around', { | |
...modifierConfig, value: asConfiguredModifier('aroundModifier', aroundModifier), | |
}); | |
Reflect.defineProperty(AsyncFunction.prototype, 'around', { | |
...modifierConfig, value: asConfiguredModifier('asyncAroundModifier', asyncAroundModifier), | |
}); | |
/* +++ BEFORE +++ and +++ ASYNC BEFORE +++ */ | |
// - The modified function invokes the custom implemented before-handler | |
// first and passes on the provided arguments only. Thus, the modified | |
// function's return value is still determined by the original function. | |
function beforeModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingBaseValidationGuard('before', proceed, handler); | |
target = target ?? null; | |
return ( | |
isAsyncFunction(proceed) || | |
isAsyncFunction(handler) | |
// delegate the modification to the async `before` implementation. | |
) && AsyncFunction.prototype.before.call(proceed, handler, target) || | |
asConfiguredModification( | |
'before', proceed, handler, | |
function /* beforeType */(...args) { | |
const context = this ?? target; | |
handler.apply(context, args); | |
return proceed.apply(context, args); | |
}, | |
); | |
} | |
function asyncBeforeModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingAsyncValidationGuard('before', proceed, handler); | |
const isAsyncProceed = isAsyncFunction(proceed); | |
target = target ?? null; | |
return asConfiguredModification( | |
'before', proceed, handler, (isAsyncProceed && | |
// - Omit `await handler.apply(...)` entirely since | |
// a before handler just gets invoked "before" the | |
// control flow proceeds with the original function. | |
// Thus, in case of an asynchronous before handler, | |
// one does not need to await its return value (or | |
// to wait for the promise to be settled). | |
// | |
// - The following implementations cover | |
// a proceed-type specific optimization. | |
async function /* asyncBeforeType */(...args) { | |
const context = this ?? target; | |
handler.apply(context, args); | |
return await proceed.apply(context, args); | |
} || | |
async function /* asyncBeforeType */(...args) { | |
const context = this ?? target; | |
handler.apply(context, args); | |
return proceed.apply(context, args); | |
}), | |
); | |
} | |
Reflect.defineProperty(Function.prototype, 'before', { | |
...modifierConfig, value: asConfiguredModifier('beforeModifier', beforeModifier), | |
}); | |
Reflect.defineProperty(AsyncFunction.prototype, 'before', { | |
...modifierConfig, value: asConfiguredModifier('asyncBeforeModifier', asyncBeforeModifier), | |
}); | |
/* +++ AFTER (RETURNING) +++ and +++ ASYNC AFTER (RETURNING) +++ */ | |
// - The modified function invokes the original function first which enables | |
// the additional passing of the original function's return value alongside | |
// the provided arguments to the custom implemented after (returning) | |
// handler. No other privileges are granted to the handler function, and | |
// its return value is not acknowledged. Thus the modified function's return | |
// value is still determined by the original function. | |
function afterReturningModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingBaseValidationGuard('after', proceed, handler); | |
target = target ?? null; | |
return ( | |
isAsyncFunction(proceed) || | |
isAsyncFunction(handler) | |
// - delegate the modification to the | |
// async `after` returning implementation. | |
) && AsyncFunction.prototype.after.call(proceed, handler, target) || | |
asConfiguredModification( | |
'after::returning', proceed, handler, | |
function /* afterReturningType */(...args) { | |
const context = this ?? target; | |
const result = proceed.apply(context, args); | |
handler.call(context, result, ...args); | |
return result; | |
}, | |
); | |
} | |
function asyncAfterReturningModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingAsyncValidationGuard('after', proceed, handler); | |
const isAsyncProceed = isAsyncFunction(proceed); | |
target = target ?? null; | |
return asConfiguredModification( | |
'after::returning', proceed, handler, (isAsyncProceed && | |
// - Omit `await handler.call(...)` entirely since | |
// an after returning handler has no other interest | |
// in the modified control flow than the original | |
// function's return value (result). | |
// | |
// - The following implementations cover | |
// a proceed-type specific optimization. | |
async function /* asyncAfterReturningType */(...args) { | |
const context = this ?? target; | |
const result = await proceed.apply(context, args) | |
handler.call(context, result, ...args); | |
return result; | |
} || | |
async function /* asyncAfterReturningType */(...args) { | |
const context = this ?? target; | |
const result = proceed.apply(context, args); | |
handler.call(context, result, ...args); | |
return result; | |
}), | |
); | |
} | |
Reflect.defineProperty(Function.prototype, 'after', { | |
...modifierConfig, value: asConfiguredModifier('afterReturningModifier', afterReturningModifier), | |
}); | |
Reflect.defineProperty(AsyncFunction.prototype, 'after', { | |
...modifierConfig, value: asConfiguredModifier('asyncAfterReturningModifier', asyncAfterReturningModifier), | |
}); | |
/* +++ AFTER THROWING +++ and +++ ASYNC AFTER THROWING +++ */ | |
// - The modified function invokes the original function via a `try...catch` | |
// statement, and in case of having been invoked successfully, the former | |
// returns the latter's invocation result. The custom implemented after | |
// throwing handler gets invoked only in case of the original function's | |
// invocation failure. The handler then gets passed the caught exception. | |
// This exception gets re-thrown as soon as the handler function returns. | |
function afterThrowingModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingBaseValidationGuard('afterThrowing', proceed, handler); | |
target = target ?? null; | |
return ( | |
isAsyncFunction(proceed) || | |
isAsyncFunction(handler) | |
// - delegate the modification to the | |
// async `afterThrowing` implementation. | |
) && AsyncFunction.prototype.afterThrowing.call(proceed, handler, target) || | |
asConfiguredModification( | |
'after::throwing', proceed, handler, | |
function /* afterThrowingType */(...args) { | |
const context = this ?? target; | |
try { | |
return proceed.apply(context, args); | |
} catch (exception) { | |
handler.call(context, exception, ...args); | |
throw exception; | |
} | |
}, | |
); | |
} | |
function asyncAfterThrowingModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingAsyncValidationGuard('afterThrowing', proceed, handler); | |
const isAsyncProceed = isAsyncFunction(proceed); | |
target = target ?? null; | |
return asConfiguredModification( | |
'after::throwing', proceed, handler, (isAsyncProceed && | |
// - Omit `await handler.call(...)` entirely since the | |
// `catch` clause and its after throwing handler have | |
// no other interest in the modified control flow than | |
// triggering the handler function and, immediatly after | |
// that, re-throwing the caught exception. | |
// | |
// - The following implementations cover | |
// a proceed-type specific optimization. | |
async function /* asyncAfterThrowingType */(...args) { | |
const context = this ?? target; | |
try { | |
return await proceed.apply(context, args); | |
} catch (exception) { | |
handler.call(context, exception, ...args); | |
throw exception; | |
} | |
} || | |
async function /* asyncAfterThrowingType */(...args) { | |
const context = this ?? target; | |
try { | |
return proceed.apply(context, args); | |
} catch (exception) { | |
handler.call(context, exception, ...args); | |
throw exception; | |
} | |
}), | |
); | |
} | |
Reflect.defineProperty(Function.prototype, 'afterThrowing', { | |
...modifierConfig, value: asConfiguredModifier('afterThrowingModifier', afterThrowingModifier), | |
}); | |
Reflect.defineProperty(AsyncFunction.prototype, 'afterThrowing', { | |
...modifierConfig, value: asConfiguredModifier('asyncAfterThrowingModifier', asyncAfterThrowingModifier), | |
}); | |
/* +++ AFTER FINALLY +++ and +++ ASYNC AFTER FINALLY +++ */ | |
// - This modified function too invokes the original function via a | |
// `try...catch` statement. The difference comes with an additionally | |
// introduced `finally` block, where the custom implemented after | |
// finally handler in addition to the provided arguments gets passed | |
// both values, the invocation `result` and the `error`. Moreover, the | |
// handle's return value becomes the modified function's return value. | |
// Thus, the modified function enables full control over how a function's | |
// after control flow is going to be handled. | |
function afterFinallyModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingBaseValidationGuard('afterFinally', proceed, handler); | |
target = target ?? null; | |
return ( | |
isAsyncFunction(proceed) || | |
isAsyncFunction(handler) | |
// - delegate the modification to the | |
// async `afterFinally` implementation. | |
) && AsyncFunction.prototype.afterFinally.call(proceed, handler, target) || | |
asConfiguredModification( | |
'after::finally', proceed, handler, | |
function /* afterFinallyType */(...args) { | |
const context = this ?? target; | |
let result; | |
let error; | |
try { | |
result = proceed.apply(context, args); | |
} catch (exception) { | |
error = exception; | |
} finally { | |
result = handler.call(context, result, error, ...args); | |
} | |
return result; | |
}, | |
); | |
} | |
function asyncAfterFinallyModifier(handler, target) { | |
'use strict'; | |
// see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
'hide source'; | |
const proceed = this; | |
runThrowingAsyncValidationGuard('afterFinally', proceed, handler); | |
const isAsyncProceed = isAsyncFunction(proceed); | |
const isAsyncHandler = isAsyncFunction(handler); | |
target = target ?? null; | |
return asConfiguredModification( | |
'after::returning', proceed, handler, | |
// - The following implementations cover a combination | |
// of proceed- and handler-type specific optimizations. | |
isAsyncProceed && ( | |
isAsyncHandler && ( | |
async function /* asyncAfterFinallyType */(...args) { | |
const context = this ?? target; | |
let result; | |
let error; | |
try { | |
result = await proceed.apply(context, args); | |
} catch (exception) { | |
error = exception; | |
} finally { | |
result = await handler.call(context, result, error, ...args); | |
} | |
return result; | |
} | |
) || ( | |
async function /* asyncAfterFinallyType */(...args) { | |
const context = this ?? target; | |
let result; | |
let error; | |
try { | |
result = await proceed.apply(context, args); | |
} catch (exception) { | |
error = exception; | |
} finally { | |
result = handler.call(context, result, error, ...args); | |
} | |
return result; | |
} | |
) | |
) || ( | |
isAsyncHandler && ( | |
async function /* asyncAfterFinallyType */(...args) { | |
const context = this ?? target; | |
let result; | |
let error; | |
try { | |
result = proceed.apply(context, args); | |
} catch (exception) { | |
error = exception; | |
} finally { | |
result = await handler.call(context, result, error, ...args); | |
} | |
return result; | |
} | |
) || ( | |
async function /* asyncAfterFinallyType */(...args) { | |
const context = this ?? target; | |
let result; | |
let error; | |
try { | |
result = proceed.apply(context, args); | |
} catch (exception) { | |
error = exception; | |
} finally { | |
result = handler.call(context, result, error, ...args); | |
} | |
return result; | |
} | |
) | |
), | |
); | |
} | |
Reflect.defineProperty(Function.prototype, 'afterFinally', { | |
...modifierConfig, value: asConfiguredModifier('afterFinallyModifier', afterFinallyModifier), | |
}); | |
Reflect.defineProperty(AsyncFunction.prototype, 'afterFinally', { | |
...modifierConfig, value: asConfiguredModifier('asyncAfterFinallyModifier', asyncAfterFinallyModifier), | |
}); | |
/* "hide source" specifc helpers and a partial polyfill for `Function.prototype.toString` itself */ | |
// - see ... [https://github.com/tc39/proposal-function-implementation-hiding] | |
const isHideSourceSupport = | |
!(/function\s+test\s*\(\s*\)\s*\{\s*['"]use\s+strict['"];\s*['"]hide\s+source['"];\s*\}/) | |
.test( | |
Function.prototype.toString.call((function test () { 'use strict'; 'hide source'; })) | |
); | |
if (!isHideSourceSupport) { | |
// - Partially re-implement `Function.prototype.toString` in order to | |
// hide the implementation details of all newly introduced/defined | |
// prototypal method-modifiers, in case they have been delegated to | |
// `Function.prototype.toString`. The re-implemantation itself does | |
// recognize its own reflection too and mimics the native behavior. | |
const hiddenSourceModifiers = new Set([ | |
aroundModifier, asyncAroundModifier, | |
beforeModifier, asyncBeforeModifier, | |
afterReturningModifier, asyncAfterReturningModifier, | |
afterThrowingModifier, asyncAfterThrowingModifier, | |
afterFinallyModifier, asyncAfterFinallyModifier, | |
]); | |
function hideSourceHandler(nativeToString, customHandler, ...args) { | |
const target = this; | |
return ( | |
(hideSourceModifier === target && 'function toString() { [native code] }') || | |
(hiddenSourceModifiers.has(target) && String(target)) || | |
nativeToString.call(target) | |
); | |
} | |
const hideSourceModifier = Function.prototype.toString.around(hideSourceHandler, Function.prototype); | |
Reflect.defineProperty(hideSourceModifier, 'name', { | |
...Reflect.getOwnPropertyDescriptor(Function.prototype.toString, 'name'), | |
}); | |
Reflect.defineProperty(hideSourceModifier, 'toString', { | |
configurable: true, value: () => 'function toString() { [native code] }', | |
}); | |
Reflect.defineProperty(Function.prototype, 'toString', { | |
//configurable: true, value: hideSourceModifier, | |
...modifierConfig, value: hideSourceModifier, | |
}); | |
} | |
// END :: module scope. | |
}()); | |
// ==ClosureCompiler== | |
// @output_file_name default.js | |
// @compilation_level SIMPLE_OPTIMIZATIONS | |
// ==/ClosureCompiler== | |
/** | |
* Original Size: 4.9KB gzipped (26.93KB uncompressed) | |
* Compiled Size: 1.61KB gzipped (6.48KB uncompressed) | |
* Saved 67.20% off the gzipped size (75.93% without gzip) | |
* The code may also be accessed at default.js. | |
* - see ... [https://closure-compiler.appspot.com/code/jscf62ed8603654d162efdf64f672dfb4b1/default.js] | |
*/ | |
'use strict';(function(){function v(b){return"function"===typeof b&&"function"===typeof b.call&&"function"===typeof b.apply}function k(b){return!!b&&"[object AsyncFunction]"===Object.prototype.toString.call(b).trim()}function G(){return`function ${Reflect.getOwnPropertyDescriptor(this,"name")?.value??""}() { [hidden source] }`}function n(b,a){Reflect.defineProperty(a,"name",{...Reflect.getOwnPropertyDescriptor(a,"name"),value:b});Reflect.defineProperty(a,"toString",{configurable:!0,value:G});return a} | |
function p(b,a,c,f){const e=Reflect.getOwnPropertyDescriptor(a,"name");Reflect.defineProperty(f,"name",{...e,value:`modified::${b} ${e.value}`});Reflect.defineProperty(f,"origin",{value:a});Reflect.defineProperty(f,"handler",{value:c});return f}function t(b,a,c,f=!1){if(!v(a))throw new TypeError(["The value delegated to",`\`${f&&"Async"||""}Function.prototype.${b}\``,"needs to be at least a `Function` instance."].join(" "));if(!v(c))throw new TypeError(["The",`${f&&"asynchronous "||""}\`${b}\``,"modifier's 1st `handler` parameter has to be at least a `Function` instance."].join(" ")); | |
}function u(b,a,c){t(b,a,c,!0);if(!k(a)&&!k(c))throw new TypeError(["In case the value delegated to",`\`AsyncFunction.prototype.${b}\``,"is a non asynchronous function type, the asynchronous",`\`${b}\``,"modifier's 1st `handler` parameter then has to be exclusively an `AsyncFunction` instance."].join(" "));if(a=k(a)&&!k(c))a=!(v(c)&&/^(?:async\s*)?(?:\(.*?\)|[^(),=]+)\s*=>/.test(Function.prototype.toString.call(c).trim()));if(a){if(a=v(c)&&!0===Object.getOwnPropertyDescriptor(c,"prototype")?.writable)a= | |
!!c&&Object.prototype.toString.call(c).trim(),a=!(a&&("[object GeneratorFunction]"===a||"[object AsyncGeneratorFunction]"===a));a=!(a&&!Function.prototype.toString.call(Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(c),"constructor").value).trim().startsWith("class "))}if(a)throw new TypeError(["In case of modifying an asynchronous function type, the",`\`${b}\``,"modifier's 1st `handler` parameter has to be either an instance of `AsyncFunction` or an arrow function expression or an ES3 function type."].join(" ")); | |
}function w(b,a){"hide source";const c=this;t("around",c,b);a=a??null;return(k(c)||k(b))&&q.prototype.around.call(c,b,a)||p("around",c,b,function(...f){return b.call(this??a,c,b,...f)})}function x(b,a){"hide source";const c=this;u("around",c,b);a=a??null;return p("around",c,b,async function(...f){return await b.call(this??a,c,b,...f)})}function y(b,a){"hide source";const c=this;t("before",c,b);a=a??null;return(k(c)||k(b))&&q.prototype.before.call(c,b,a)||p("before",c,b,function(...f){const e=this?? | |
a;b.apply(e,f);return c.apply(e,f)})}function z(b,a){"hide source";const c=this;u("before",c,b);const f=k(c);a=a??null;return p("before",c,b,f&&async function(...e){const d=this??a;b.apply(d,e);return await c.apply(d,e)}||async function(...e){const d=this??a;b.apply(d,e);return c.apply(d,e)})}function A(b,a){"hide source";const c=this;t("after",c,b);a=a??null;return(k(c)||k(b))&&q.prototype.after.call(c,b,a)||p("after::returning",c,b,function(...f){const e=this??a,d=c.apply(e,f);b.call(e,d,...f); | |
return d})}function B(b,a){"hide source";const c=this;u("after",c,b);const f=k(c);a=a??null;return p("after::returning",c,b,f&&async function(...e){const d=this??a,g=await c.apply(d,e);b.call(d,g,...e);return g}||async function(...e){const d=this??a,g=c.apply(d,e);b.call(d,g,...e);return g})}function C(b,a){"hide source";const c=this;t("afterThrowing",c,b);a=a??null;return(k(c)||k(b))&&q.prototype.afterThrowing.call(c,b,a)||p("after::throwing",c,b,function(...f){const e=this??a;try{return c.apply(e, | |
f)}catch(d){throw b.call(e,d,...f),d;}})}function D(b,a){"hide source";const c=this;u("afterThrowing",c,b);const f=k(c);a=a??null;return p("after::throwing",c,b,f&&async function(...e){const d=this??a;try{return await c.apply(d,e)}catch(g){throw b.call(d,g,...e),g;}}||async function(...e){const d=this??a;try{return c.apply(d,e)}catch(g){throw b.call(d,g,...e),g;}})}function E(b,a){"hide source";const c=this;t("afterFinally",c,b);a=a??null;return(k(c)||k(b))&&q.prototype.afterFinally.call(c,b,a)|| | |
p("after::finally",c,b,function(...f){const e=this??a;let d,g;try{d=c.apply(e,f)}catch(h){g=h}finally{d=b.call(e,d,g,...f)}return d})}function F(b,a){"hide source";const c=this;u("afterFinally",c,b);const f=k(c),e=k(b);a=a??null;return p("after::returning",c,b,f&&(e&&async function(...d){const g=this??a;let h,l;try{h=await c.apply(g,d)}catch(r){l=r}finally{h=await b.call(g,h,l,...d)}return h}||async function(...d){const g=this??a;let h,l;try{h=await c.apply(g,d)}catch(r){l=r}finally{h=b.call(g,h, | |
l,...d)}return h})||e&&async function(...d){const g=this??a;let h,l;try{h=c.apply(g,d)}catch(r){l=r}finally{h=await b.call(g,h,l,...d)}return h}||async function(...d){const g=this??a;let h,l;try{h=c.apply(g,d)}catch(r){l=r}finally{h=b.call(g,h,l,...d)}return h})}const q=Reflect.getOwnPropertyDescriptor(Object.getPrototypeOf(async function(){}),"constructor").value,m={writable:!0,configurable:!0};Reflect.defineProperty(Function.prototype,"around",{...m,value:n("aroundModifier",w)});Reflect.defineProperty(q.prototype, | |
"around",{...m,value:n("asyncAroundModifier",x)});Reflect.defineProperty(Function.prototype,"before",{...m,value:n("beforeModifier",y)});Reflect.defineProperty(q.prototype,"before",{...m,value:n("asyncBeforeModifier",z)});Reflect.defineProperty(Function.prototype,"after",{...m,value:n("afterReturningModifier",A)});Reflect.defineProperty(q.prototype,"after",{...m,value:n("asyncAfterReturningModifier",B)});Reflect.defineProperty(Function.prototype,"afterThrowing",{...m,value:n("afterThrowingModifier", | |
C)});Reflect.defineProperty(q.prototype,"afterThrowing",{...m,value:n("asyncAfterThrowingModifier",D)});Reflect.defineProperty(Function.prototype,"afterFinally",{...m,value:n("afterFinallyModifier",E)});Reflect.defineProperty(q.prototype,"afterFinally",{...m,value:n("asyncAfterFinallyModifier",F)});if(/function\s+test\s*\(\s*\)\s*\{\s*['"]use\s+strict['"];\s*['"]hide\s+source['"];\s*\}/.test(Function.prototype.toString.call(function(){"hide source"}))){const b=new Set([w,x,y,z,A,B,C,D,E,F]),a=Function.prototype.toString.around(function(c, | |
f,...e){return a===this&&"function toString() { [native code] }"||b.has(this)&&String(this)||c.call(this)},Function.prototype);Reflect.defineProperty(a,"name",{...Reflect.getOwnPropertyDescriptor(Function.prototype.toString,"name")});Reflect.defineProperty(a,"toString",{configurable:!0,value:()=>"function toString() { [native code] }"});Reflect.defineProperty(Function.prototype,"toString",{...m,value:a})}})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment