How to use withLatestFrom with a promise?

1.2k Views Asked by At

Why isn't it possible to convert a promise to an observable and then use it with withLatestFrom. It's not clear to me, why this doesn't resolve.

of('')
  .pipe(
    withLatestFrom(from(myPromise)),
    map((res) => {
      console.log('does not resolve', res);
    })
  )
  .subscribe();
3

There are 3 best solutions below

3
On BEST ANSWER

WithLatestFrom emits only when its source Observable emits, since of('') emits before the source to withLatestFrom, your withLatestFrom is never triggered.

The following code will not work:

of('1 + 1')
  .pipe(
    withLatestFrom(new Promise(x => x("== 2"))),
    map(([res1,res2]) => console.log(res1, res2))
  )
  .subscribe();

but this will:

of('1 + 1')
  .pipe(
    delay(100), // <= delay source execution
    withLatestFrom(new Promise(x => x("== 2"))),
    map(([res1,res2]) => console.log(res1, res2))
  )
  .subscribe();
1
On

This is expected behaviour for withLatestFrom. The source observable completes synchronously before the promise resolves (asynchronously). withLatestFrom only emits once both observables have emitted at least once and then only emits when the source observable emits.

In your case, the source observable is complete before the promise resolves.

of('').pipe(
  delay(100),
  withLatestFrom(from(myPromise)),
  tap(res =>
    console.log('should resolve', res)
  )
).subscribe();

Update #1 : solutions without delay

If both source observables emit once and complete, then forkJoin is best

forkJoin({
  addition: of(1 + 1),
  promise: from(myPromise)
}).pipe(
  map(({addition, promise}) => {return /* something */})
)

combineLatest is similar to forkJoin but it works better with long-lived streams that keep emitting values. It waits for its inner streams to all start emitting and then emits all their latest values.

combineLatest(
  of(1 + 1),
  from(myPromise)
).pipe(
  map(([addition, promise]) => {return /* something */})
)

Or, if you want to use withLatestFrom and your comfortable with the timing constraints.

from(myPromise).pipe(
  withLatestFrom(of(1+1)),
  map(([promise, addition]) => {return /* something */})
)

should work.

0
On

As Rafi pointed out in his answer, this fails because the promise will only resolves too late.

Another solution to the ones mentioned in his answer is to use one of the higher-order mapping operators (switchMap, mergeMap, concatMap or exhaustMap) to add the value the promise resolves to to the observable pipe:

const promise = Promise.resolve('promise');
of('observable')
  .pipe(switchMap(async (value) => [value, await promise]))
  .subscribe(console.log);

Alternatively, which is the simplest solution by far as long as the promise value is only needed at the end of the chain, is to convert the subscriber into an async function (which may require a linter rule complaining about the mismatch between void and Promise<void> to be disabled, as the returned promise will now be ignored):

const promise = Promise.resolve('promise');
of('observable').subscribe(async (observableValue) => {
  const promiseValue = await promise;
  console.log(observableValue, promiseValue);
});