Собственные декораторы
В предыдущих главах мы использовали встроенные декораторы @operation и @daemon для расширения ф ункциональности методов сервиса. Но что если нам нужна кастомная логика, которая будет применяться к множеству методов? В этом случае мы можем создать собственные декораторы.
Настройка TypeScript
Sagun использует legacy-версию декораторов TypeScript (Stage 2), а не новый стандарт декораторов из ECMAScript. Это связано с тем, что новый стандарт не предусматривает декорирование аргументов, что необходимо для реализации DI
Для работы декораторов убедитесь, что в вашем tsconfig.json включена опция experimentalDecorators:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
Преимущества декораторов
Декораторы предоставляют мощный механизм для расширения функциональности без изменения исходного кода методов:
- Переиспользуемость — один раз написанный декоратор можно применить к любому количеству методов
- Декларативность — логика расширения явно указана над методом, что улучшает читаемость кода
- Разделение ответственности — основная логика метода остается чистой, а дополнительная функциональность вынесена в декоратор
- Композиция — декораторы можно комбинировать, наращивая функциональность слоями
Создание своего декоратора
Рассмотрим практический пример — создадим декоратор @log, который будет логировать вызовы методов в консоль.
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const origin = descriptor.value;
// Оборачиваем оригинальный метод
function* logged(...args: any[]) {
console.log(`▶ [${target.toString()}] ${key} вызван с аргументами:`, args);
try {
// Вызываем оригинальный метод с сохранением контекста
const result = yield* call([this, origin], ...args);
console.log(`✓ [${target.toString()}] ${key} вернул:`, result);
return result;
} catch (error) {
console.error(`✗ [${target.toString()}] ${key} ошибка:`, error);
throw error;
}
}
// Сохраняем свойства оригинального метода (например, id операции)
descriptor.value = Object.assign(logged, origin);
return descriptor;
}
Теперь применим наш декоратор к сервису из предыдущих примеров:
class OrderService extends Service {
toString() {
return "OrderService"
}
@log
@operation
*getOrders() {
return yield* call(fetchOrders);
}
@log
@daemon()
*addOrder() {
const id = getNewId();
yield* call(addOrder, {id, description: `Order ${id}`});
yield* call(this.getOrders);
}
}
Полный пример
Откройте консоль браузера (F12 → Console), чтобы увидеть логи вызовов методов.
Порядок декораторов
Декораторы в TypeScript применяются снизу вверх — сначала применяется самый нижний декоратор, затем его оборачивает следующий и так далее. Это важно понимать при композиции нескольких декораторов.
Рассмотрим это на примере двух декораторов: @log и @catch. Декоратор @catch перехватывает ошибки и возвращает fallback-значение вместо выброса исключения:
function errorHandler(fallback: any) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
const origin = descriptor.value;
function* caught(...args: any[]) {
try {
return yield* call([this, origin], ...args);
} catch (error) {
console.warn(`[${key}] ошибка перехвачена, возвращаем fallback`);
return fallback;
}
}
descriptor.value = Object.assign(caught, origin);
return descriptor;
};
}
Вариант 1: @log выше @errorHandler
@log
@errorHandler([])
@operation
*getOrders() { ... }
Порядок применения (снизу вверх): operation → errorHandler → log
В этом случае @log оборачивает @catch, поэтому:
- Если метод выбросит ошибку,
@catchеё перехватит @logувидит успешный результат (fallback-значение)- Ошибка НЕ будет залогирована
Вариант 2: @catch выше @log
@errorHandler([])
@log
@operation
*getOrders() { ... }
Порядок применения (снизу вверх): operation → log → errorHandler
В этом случае @catch оборачивает @log, поэтому:
- Если метод выбросит ошибку,
@logеё увидит и залогирует - Затем ошибка "всплывёт" к
@catch, который её перехватит - Ошибка БУДЕТ залогирована
Интерактивный пример
Попробуйте изменить порядок декораторов @log и @errorHandler в файле OrderService.ts и нажмите кнопку "Сломать загрузку". Обратите внимание на консоль — в одном случае ошибка логируется, в другом нет.
С помощью порядка декораторов можно очень гибко настраивать поведение методов просто меняя строки местами - что логировать, какие ошибки всплывают, валидации и т.д.