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

TL;DR:
šØ 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 !
š 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
setInterval
š§Ŗ The experiment
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
public async transformToResult(resultOrDeferred: any) {
if (resultOrDeferred && isFunction(resultOrDeferred.subscribe)) {
return resultOrDeferred.toPromise();
}
return resultOrDeferred;
}
š Detecting request cancelation
@Injectable()
export class NoopInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
return next.handle();
}
}
NestJS interceptors are Observable friendly
next
@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$));
}
}