Illorian
@Illorian
Front-end разработчик

Как не потерять контекст функции, используя декораторы в Typescript?

Ситуация следующая. У меня есть декоратор класса, которые устанавливает начальные значения настроек логирования. Есть пара методов, которые как раз и отвечают за то, как и что будет логироваться. Основная проблема в том, что при вызове функции originValue у меня теряются поля класса.

export function LoggerInit(options: ILoggerClass = defaultClassLoggerOptions) {
	options = Object.assign({}, defaultClassLoggerOptions, options);
	return function (target) {
		target.prototype.logger          = log;
		target.prototype.__loggerOptions = options;
		target.prototype.__className     = options.name || target.name;
	}
}

export function debug(options: ILoggerMethod = defaultMethodLoggerOptions) {
	options = Object.assign({}, defaultMethodLoggerOptions, options);
	return function (target: Object,
	                 propertyKey: string | symbol,
	                 descriptor: TypedPropertyDescriptor<any>) {
		const originValue = descriptor.value;
		descriptor.value  = function (...args) {
			return descriptorWrapper.call(this, "debug", originValue, options, propertyKey, args);
		};
		return descriptor;
	}
}

async function descriptorWrapper(logMethod,
                                 originValue,
                                 options,
                                 propertyKey,
                                 args) {
	let res;
	let isPromise: boolean;
	try {
		res       = await originValue.call(this, args);
		isPromise = true;
	}
	catch (e) {
		res       = originValue.call(this, args);
		isPromise = false;
	}
	const classOptions: ILoggerClass = this.__loggerOptions;
	if (classOptions && classOptions.enabled && options.enabled) {
		const className = this.__className;
		const data      = [`[${className}].${options.name || propertyKey}`];
		
		if (options.withParams && !!args && !!args.length) {
			data.push(`params: ${args.map(
				a => JSON.stringify(a))
			                         .join()}`);
		}
		
		if (options.withResult) {
			data.push(`result ${JSON.stringify(res)}`);
		}
		
		log[logMethod](data.join(" | "));
	}
	return (isPromise) ? Promise.resolve(res) : res;
}


Примерный пример использования:
@LoggerInit({name: "Test"})
class MegaTest {
   private logger: Logger;
   private url = "localhost";

   @debug()
   getAll() {
        this.logger.info("try to get all"); // тут ошибка
        return Promise.resolve("test");
   }

   @debug()
   getById(id: number) {
       return service.get(this.url); // тут ошибка
   }
}
  • Вопрос задан
  • 374 просмотра
Пригласить эксперта
Ответы на вопрос 2
Вам разве не нужно сделать originValue.call(this, args) вместо какого-то Object.assgin, который вам смержит ваш текущий контекст с аргументами функции.
Ответ написан
Illorian
@Illorian Автор вопроса
Front-end разработчик
По итогу заработало так. Metadata - обёртка над Reflect.

export function LoggerInit(options: ILoggerClass = defaultClassLoggerOptions): ClassDecorator {
	options = Object.assign({}, defaultClassLoggerOptions, options);
	return function (target) {
		target.prototype.logger = log;
		options.name            = options.name || target.name;
		Metadata.set("__loggerOptions", options, target);
	}
}

export function debug(options: ILoggerMethod = defaultMethodLoggerOptions): MethodDecorator {
	options = Object.assign({}, defaultMethodLoggerOptions, options);
	return function (target: Object,
	                 propertyKey: string | symbol,
	                 descriptor: TypedPropertyDescriptor<any>) {
		return {
			value: function (...args) {
				return descriptorWrapper.call(this, "debug", descriptor.value, options, propertyKey, args);
			}
		}
	}
}

async function descriptorWrapper(logMethod,
                                 originValue,
                                 options,
                                 propertyKey,
                                 args) {
	let res;
	let isPromise: boolean;
	try {
		res       = await originValue.apply(this, args);
		isPromise = true;
	}
	catch (e) {
		res       = originValue.apply(this, args);
		isPromise = false;
	}
	const classOptions = Metadata.get("__loggerOptions", this);
	if (classOptions && classOptions.enabled && options.enabled) {
		const data = [`[${classOptions.name}].${options.name || propertyKey}`];
		
		if (options.withParams && !!args && !!args.length) {
			data.push(`params: ${args.map(
				a => JSON.stringify(a))
			                         .join()}`);
		}
		
		if (options.withResult) {
			data.push(`result ${JSON.stringify(res)}`);
		}
		
		log[logMethod](data.join(" | "));
	}
	return (isPromise) ? Promise.resolve(res) : res;
}
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы