Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJS delay at least X milliseconds

I'm trying to implement the following behavior in RxJS:

  1. Fire an event
  2. Call an http API
  3. When the API returns, either:
    1. Wait until at least X milliseconds have passed since firing the event
    2. Return immediately if X milliseconds have already passed since firing the event

This would is very useful for UX, because even if the call takes 1ms, I'd like to show a loading icon for at least 100ms.

I haven't found any way to implement this either with delay, throttle, debounce or its variations.

this.eventThatFires
    .switchMap(data => {
        let startTime = Date.now();
        return this.callHttpService(data)
            .delay(new Date(startTime + 1000));
    })

I assumed something like this worked, but using an absolute date seems to do a time difference with the current time, and not schedule the delay for that absolute time.


EDIT:

It seems there's no built-in operator that works as what I describe. I just created it because I'll be using it a lot throughout my application:

import { Observable } from "rxjs/Observable";

function delayAtLeast<T>(this: Observable<T>, delay: number): Observable<T> {
    return Observable.combineLatest(
        Observable.timer(delay),
        this)
    .map(([_, i]) => i);
}

Observable.prototype.delayAtLeast = delayAtLeast;

declare module "rxjs/Observable" {
    interface Observable<T> {
        delayAtLeast: typeof delayAtLeast;
    }
}
like image 585
Martín Coll Avatar asked Apr 07 '17 18:04

Martín Coll


2 Answers

I made a custom rxjs operator based off of Martin's final solution.

import { combineLatest, Observable, OperatorFunction, timer } from "rxjs";
import { map } from "rxjs/operators";

export function delayAtLeast<T>(delay: number): OperatorFunction<T, T> {
  return function(source$: Observable<T>): Observable<T> {
    return combineLatest([timer(delay), source$]).pipe(map(x => x[1]));
  }
}
like image 52
Justin Ipson Avatar answered Oct 01 '22 13:10

Justin Ipson


What is wrong with your combineLatest solution?

You can also use zip:

this.eventThatFires
    .switchMap(data => Observable.zip(
        this.profileService.updateInfo(profileInfo)),
        Observable.timer(500),
        x => x));
like image 26
Brandon Avatar answered Oct 01 '22 15:10

Brandon