1

My desired behaviour:

  1. Run HTTP request
  2. Immediately look up data in async cache
  3. If cache has the value before HTTP emits - use cache value.
  4. Use HTTP value after it's finally here.
  5. If HTTP responds faster than cache - ignore cache.

So basically I would like to kick off two async processes, one of which is supposed to provide a value quickly but if it doesn't - I want to only use the value from a slower observable which takes precedence anyway.

devmiles.com
  • 9,895
  • 5
  • 31
  • 47
  • 1
    You're looking for either `combineLatest` or `forkJoin` function. But it depends on how the "_async cache_" you mention is configured. Is it a stream or does it only emit one notification and complete? Could you show what have you tried so far? – ruth Feb 02 '22 at 13:13
  • 1
    wouldn't `combineLatest` only fire after both observables emit? that's not what I'm looking for. I've tried this: `const combo$ = cache$.pipe(takeUntil(server$)).pipe(concat(server$))`; this seems to create two subscriptions to server$. – devmiles.com Feb 02 '22 at 13:25
  • Yes, that's true. Both the functions would only emit when both observables have emitted. However with your current implementation the inner observable `server$` will only be triggered after the outer observable (`cache$`) has emitted. So question is to trigger two observables in parallel and utilize the first emission even if the other observable hasn't emitted yet. – ruth Feb 02 '22 at 13:57
  • yes, correct! that's how my attempt at solution works – devmiles.com Feb 02 '22 at 14:19
  • Tried the following after using `shareReplay(1)` on `server$`: `const combo$ = merge(server$, cache$.pipe(takeUntil(server$)));` – devmiles.com Feb 02 '22 at 14:24

2 Answers2

1

To expand from my comments: the question is to trigger two observables in parallel and utilize the first emission even if the other observable hasn't emitted yet.

Normally you could use the merge function for it.

However you have a condition ("If HTTP responds faster than cache - ignore cache.") that is not natively fulfilled by the merge function nor by any standard RxJS operators.

But it is easy to write custom operators in RxJS from existing operators. For your case you could customize the filter operator to suit your needs. See here for a brief intro on how to write a custom operator.

export const filterLateCache = () => {
  let serverEmitted = false;
  return <T>(source: Observable<T>) => {
    return source.pipe(
      filter((data: any) => {
        if (!!data.server) {
          serverEmitted = true;
          return true;
        } else if (!!data.cache) {
          if (serverEmitted) {
            return false;
          } else {
            return true;
          }
        } else {
          return false;
        }
      })
    );
  };
};

As you can see the boolean flags server and cache in the incoming notification are checked to decide whether the value must be emitted. So you'd need to append the values from the observables with these flags using the map operator.

merge(
  server$.pipe(
    map((value) => ({
      server: true,
      value: value,
    }))
  ),
  cache$.pipe(
    map((value) => ({
      cache: true,
      value: value,
    }))
  )
)
  .pipe(filterLateCache())
  .subscribe({
    next: ({ value }) => {       // <-- utilize destructuring to ignore boolean flags
      // handle response here
    },
    error: (error: any) => {
      // handle errors
    }
  });

Working example: Stackblitz

ruth
  • 29,535
  • 4
  • 30
  • 57
  • 1
    I went with my shareReplay solution in the comment to the question, but this answer is really good, so I'll accept it. – devmiles.com Feb 02 '22 at 17:23
0

Maybe it is worth looking at the raceWith: https://rxjs-dev.firebaseapp.com/api/operators/raceWith

Basically it would look like:

server$.pipe(raceWith(cache$)).subscribe(/*side effect that must be done*/);

The thing missing is that it does not fulfill requirement 4.

daflodedeing
  • 319
  • 3
  • 11
  • Sorry, I have seen my mistake: `raceWith`will then subscribe forever to the observable that emits first, which does not fulfill the requirements. – daflodedeing Feb 02 '22 at 15:35