
End-to-End HTTP request cancelation with RxJS & NestJS

TL;DR:
if you are using NestJS , you will need this interceptor , if you are not using NestJS then maybe you should , we have to think reactively , I agree that it can have a steep learning curve but think about the pleasure of sliding on the other side of the hill ⛷, we can and should use RxJS everywhere , we should use observables even for single value streams , we should not ignore observables teardown logic .
🚨 Reactive Programming & RxJS to the rescue
keywords$ = this.keywordsControl.valueChanges;
data$ = keywords$.pipe(
/* Wait for the user to stop typing for 100ms and emit last value. */
debounceTime(100),
/* Ignore identical successive values
* (e.g. user pastes the same value in the input). */
distinctUntilChanged(),
/* when new keywords are emitted, this unsubscribes from the previous
* search result (canceling the underlying http request)
* and subscribes to the new one. */
switchMap(keywords => this.search(keywords))
)
🐢 Hey Debounce ! Stop bullying my low latency !
debounceTime
switchMap
🐈 Here comes the big cat
🧨 Observables Teardown Logic
setInterval
function interval(period) {
return new Observable(observer => {
let i = 0;
const handle = setInterval(() => observer.next(i++), period);
/* This is the teardown logic. */
return () => clearInterval(handle);
});
}
Observable
clearInterval
setInterval
interval
timer
🧪 The experiment
node_modules
function getFiles(directoryPath) {
return new Observable(observer => {
...
return () => walker.pause();
}
}
function readLines(filePath) {
return new Observable(observer => {
...
return () => reader.close();
}
}
function search(): Observable<Line[]> {
return getFiles(nodeModulesPath)
.pipe(
mergeMap(file => readLines(file)),
...
);
}
😔 The disappointment
toPromise
public async transformToResult(resultOrDeferred: any) {
if (resultOrDeferred && isFunction(resultOrDeferred.subscribe)) {
return resultOrDeferred.toPromise();
}
return resultOrDeferred;
}
🔍 Detecting request cancelation
IncomingMessage
close
aborted
vs close
: IncomingMessage
also triggers an aborted
event that you can ignore as it will probably be deprecated in the future. Cf. https://github.com/nodejs/node/issues/15456 & https://github.com/nodejs/node/issues/15525 .
Interceptors have a set of useful capabilities which are inspired by the Aspect Oriented Programming (AOP) technique.
@Injectable()
export class NoopInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
return next.handle();
}
}
intercepts every incoming HTTP request, listens to the request’s close
event, does something to interrupt the work.
NestJS interceptors are Observable friendly
next
handle
Observable
Observable
close
fromEvent
Observable
takeUntil
@Injectable()
export class UnsubscribeOnCloseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
if (context.getType() !== 'http') {
return next.handle();
}
const request = context.switchToHttp().getRequest() as Request;
const close$ = fromEvent(request, 'close');
return next.handle().pipe(takeUntil(close$));
}
}