experimentalDecorators: "true"как я могу сделать что-то вроде:
class LambdaFunctionExample {
lambdaResponse = [1, 2 , 3]
@tryCatchWrapper
async handler(event) {
const data = this.lambdaResponse;
const result = await fetch(...)
return {
statusCode: 200,
data,
result
};
}
}
и перехватывать ошибки/события для целей POC.
Похожая проблема, решенная без декораторов, но с модификацией метода, была задана как «Как расширить/обернуть/перехватить/украсить класс (с помощью специальных функций, таких как трассировка)»



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


для целей POC, как уже упоминалось
Я использую:
class LambdaFunctionExample {
lambdaResponse = [1, 2, 3]
@tryCatchWrapper
async handler(event) {
const data = this.lambdaResponse;
const result = await fetch(...)
return {
statusCode: 200,
data,
result
};
}
}
export const handler = (event) => new LambdaFunctionExample().handler(event)
функция декоратора:
export function tryCatchWrapper<T>(target: Function, { kind, name, ...context }) {
if (kind === 'method')
return async function (this: unknown, ...args: any) {
try {
console.info('name', name)
return await target.call(this, ...args)
} catch (error) {
console.info('error catched')
return {}
}
}
}
и бег
npm start # node index.js (as usual)
.tsconfig
"compilerOptions": {
// Module Setup
"target": "ES2022", // as of today, I tried with ESNext and it did't work, so I need to use strictly ES2022
//"module": "NodeNext", (also optional, but you imporant if you are using ESM)
//"moduleResolution": "NodeNext", (also optional, but you imporant if you are using ESM)
...
}
Спасибо за предоставление вышеуказанного решения. +1
Поскольку вопрос ОП также касается функций декоратора как общего шаблона, а также из-за предложения/реализации декоратора JavaScript, специально ориентированного на функции класса, существует пробел, который может быть заполнен специализированными модификаторами методов, методами, которые реализованы в Function.prototype соответственно AsyncFunction.prototype.
Очевидными вариантами использования обертывания функций или методов, независимо от того, являются ли они синхронными или асинхронными, являются вокруг , до , after (покрытие после возврата), afterThrowing и afterFinally.
Вариант использования ОП прекрасно описан afterFinally.
А следующий примерный код берет пример класса OP и изменяет его в единую реализацию, которая служит испытательным стендом для настраиваемого/изменяемого поведения его единственной реализованной прототипной функции handler.
async function mockedFetch(isFetchFails) {
return (isFetchFails === true)
? new Promise((_, reject) => setTimeout(reject, 3000, 'request timed out'))
: new Promise(resolve => setTimeout(resolve, 1500, 'successfully fetched result'));
}
function afterFinallyHandler(result, exception = null, ...args) {
// - the next following code block serves the demonstration purpose only.
const reason = (typeof exception === 'string') && exception || null;
const failure = (reason && { reason }) || (exception && { exception }) || {};
console.info({
args, value: result, ...failure
});
// - the next following single line of code covers the OP's
// actual implementation effort, since everything else got
// already abstracted away by the `afterFinally` modifier.
return (exception === null) ? result : {};
}
class HandlerMethodExample {
#isFetchFails = false;
#lambdaResponse = [1, 2 , 3];
constructor({ isFetchFails = false, isModifyHandler = false } = {}) {
// implement modification depending on e.g. passed arguments.
if (isModifyHandler) {
this.handler = Object
.getPrototypeOf(this)
// the prototypal async `handler` method.
.handler
// modifying the prototypal async `handler` method.
.afterFinally(afterFinallyHandler, this);
}
this.#isFetchFails = !!isFetchFails;
}
async handler(event) {
const data = this.#lambdaResponse;
const result = await mockedFetch(this.#isFetchFails);
return { statusCode: 200, data, result };
}
}
(async () => {
const failingAndUnmodifiedHandlerExample =
new HandlerMethodExample({ isFetchFails: true });
const notFailingAndUnmodifiedHandlerExample =
new HandlerMethodExample();
const failingButModifiedHandlerExample =
new HandlerMethodExample({ isFetchFails: true, isModifyHandler: true });
const notFailingButModifiedHandlerExample =
new HandlerMethodExample({ isModifyHandler: true });
let testResult;
// console.info('\ntrigger async `failingAndUnmodifiedHandlerExample.handler` ...');
// testResult = await failingAndUnmodifiedHandlerExample.handler('skipped', 'test');
// console.info('... testResult ...', testResult);
console.info('\ntrigger async `notFailingAndUnmodifiedHandlerExample.handler` ...');
testResult = await notFailingAndUnmodifiedHandlerExample.handler('1st', 'test');
console.info('... testResult ...', testResult);
console.info('\ntrigger async `failingButModifiedHandlerExample.handler` ...');
testResult = await failingButModifiedHandlerExample.handler('2nd', 'test');
console.info('... testResult ...', testResult);
console.info('\ntrigger async `notFailingButModifiedHandlerExample.handler` ...');
testResult = await notFailingButModifiedHandlerExample.handler('3rd', 'test');
console.info('... testResult ...', testResult);
})();.as-console-wrapper { min-height: 100%!important; top: 0; }<script>
(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 () {});
/**
* 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 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 */
/**
* 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;
}
const modifierConfig = { writable: true, configurable: true };
/* 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(' '));
}
}
/* +++ AFTER FINALLY +++ and +++ ASYNC AFTER FINALLY +++ */
// - This modified function invokes the original function via a
// `try...catch` statement. Within the `finally` block 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: afterFinallyModifier,
});
Reflect.defineProperty(AsyncFunction.prototype, 'afterFinally', {
...modifierConfig, value: asyncAfterFinallyModifier,
});
// END :: module scope.
}());
// console.info(Function.prototype.afterFinally);
// console.info((async function () {}).constructor.prototype.afterFinally);
</script>Предоставление причины, например технической или концептуальной ошибки, для отрицательного голосования по приведенному выше ответу действительно помогает аудитории как в рассуждениях о моем ответе, так и в улучшении самого ответа. Кроме того, голос -1 был получен через 5 минут после моего ответа. Я сомневаюсь, что избиратель сможет выработать хорошо профинансированное техническое/концептуальное суждение за такой короткий период времени.
Всегда ли приведенная выше реализация после транспилирования должна включать дополнительную функциональность, применяемую хуком-декоратором?