I'm trying to create a decorator that requires dependency injection. For example:
@Injectable()
class UserService{
@TimeoutAndCache(1000)
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}
The @TimeoutAndCache returns a new promise which does the following:
export const TimeoutAndCache = function timeoutCache(ts: number, namespace) {
return function log(
target: object,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>,
) {
const originalMethod = descriptor.value; // save a reference to the original method
descriptor.value = function(...args: any[]) {
// pre
let timedOut = false;
// run and store result
const result: Promise<object> = originalMethod.apply(this, args);
const task = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
if (!timedOut) {
timedOut = true;
console.log('timed out before finishing');
reject('timedout');
}
}, ts);
result.then(res => {
if (timedOut) {
// store in cache
console.log('store in cache');
} else {
clearTimeout(timer);
// return the result
resolve(res);
}
});
});
return task;
};
return descriptor;
};
};
I need to inject a RedisService to save the evaluated result. One way I could inject Redis Service in to the UserService, but seems kind ugly.
You should consider using an Interceptor
instead of a custom decorator as they run earlier in the Nest pipeline and support dependency injection by default.
However, because you want to both pass values (for cache timeout) as well as resolve dependencies you'll have to use the mixin
pattern.
import {
ExecutionContext,
Injectable,
mixin,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { TestService } from './test/test.service';
@Injectable()
export abstract class CacheInterceptor implements NestInterceptor {
protected abstract readonly cacheDuration: number;
constructor(private readonly testService: TestService) {}
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
// Whatever your logic needs to be
return call$;
}
}
export const makeCacheInterceptor = (cacheDuration: number) =>
mixin(
// tslint:disable-next-line:max-classes-per-file
class extends CacheInterceptor {
protected readonly cacheDuration = cacheDuration;
},
);
You would then be able to apply the Interceptor to your handler in a similar fashion:
@Injectable()
class UserService{
@UseInterceptors(makeCacheInterceptor(1000))
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}