Careeroza
RxJS Basics for Interceptors
Observables in One Minute
NestJS wraps every handler result in an RxJS Observable, a stream of values. In interceptors you mostly need three operators:
| Operator | Does | Interceptor use |
|---|---|---|
map(fn) | Replace each value with fn(value) | Wrap the response { data } |
tap(fn) | Run a side effect, keep the value | Logging, metrics |
catchError(fn) | Handle an error, return a new stream | Convert errors |
timeout(ms) | Error if nothing arrives in time | Request timeouts |
A Tiny Observable
Example
rxjs is not available here, so this is a mini Observable with pipe, map and tap. It behaves like the real thing for these operators:
type Observer<T> = (value: T) => void;
type Operator<A, B> = (source: Observable<A>) => Observable<B>;
class Observable<T> {
constructor(private producer: (next: Observer<T>) => void) {}
subscribe(next: Observer<T>) { this.producer(next); }
pipe<A>(op1: Operator<T, A>): Observable<A>;
pipe<A, B>(op1: Operator<T, A>, op2: Operator<A, B>): Observable<B>;
pipe(...ops: Operator<any, any>[]): Observable<any> {
return ops.reduce((src, op) => op(src), this as Observable<any>);
}
}
const of = <T>(value: T) => new Observable<T>((next) => next(value));
const map = <A, B>(fn: (a: A) => B): Operator<A, B> => (src) => new Observable((next) => src.subscribe((v) => next(fn(v))));
const tap = <A>(fn: (a: A) => void): Operator<A, A> => (src) => new Observable((next) => src.subscribe((v) => { fn(v); next(v); }));
// what next.handle() would return for a controller that returns a product
const handlerResult = of({ id: 7, name: 'boAt Airdopes 141', price: 1299 });
handlerResult
.pipe(
tap((p) => console.log('[log] handler returned product', p.id)),
map((p) => ({ success: true, data: p })),
)
.subscribe((body) => console.log('response body:', JSON.stringify(body)));Output
[log] handler returned product 7
response body: {"success":true,"data":{"id":7,"name":"boAt Airdopes 141","price":1299}}