What's more appropriate - concat or map?

Viewed 163

I want to execute 2 promises, then pass the result of promise2 to a bunch of other rxjs operators to do a bunch of different things if promise2 resolves. But I want promise2 to execute only if promise1 resolves. To get this effect, should I be using something like

from(promise1).pipe(
map(x => promise2),
tap((x) => console.log('response from map: ', x)),
other_rxjs_ops)
  .subscribe(...)

If I use concat instead of map

concat(promise1, defer(() => promise2)).pipe(
tap(console.log('response from map: ', x)),
other_rxjs_ops))
  .subscribe(...)

I'm presuming what will happen is that promise1 will execute, and if it resolves, promise2 will execute, and results from both promises will then go through the other rxjs operators and be subscribed to, right?

So is my approach with map for the desired result appropriate or should I be thinking of another rxjs operator to work with in executing promise1 and promise2? The one issue I run into when using map is that the console.log inside tap seems to execute before the promise inside map has resolved or errored out, and console prints the following:

response from map: undefined //presuming promise2 hasn't had a chance to resolve yet, which is why we're getting undefined because the promise is designed to return a string value.

Using concatMap instead of map addresses this issue, but I don't understand why tap behaves the way it does when used with map. Any guidance on that would be helpful also.

2 Answers

In your case, you can use one of the Higher-Order RxJS mapping operators, such as concatMap, mergeMap, or switchMap, to merge streams to each other.

You can try something like the following:

from(promise1)
  .pipe(
    // promise2 won't be executed until the promise1 is resolved.
    concatMap((val1) => from(promise2)),
    // tap won't be executed until the promise2 is resolved.
    tap((val2) => console.log('response from promise2:', val2))
    // Other RxJS operators here...
  )
  .subscribe();

Notes:

  • The map operator is used to transform the value to another type, and in your case, it's used to transform the promise1 result to a Promise, however, it doesn't subscribe to it nor convert it to an Observable, so the tap is executed directly after transforming it, without waiting it to be resolved.
  • (Optional) To be able to subscribe to the promise1, it should be transformed to Observable using from function.
  • To subscribe to the new Observable it should be merged with the original from(promise1) one, using one of the higher-order mapping operators not using the normal map.

About your 1st option

You are using map operator and seeing tap is not waiting for promise to be resolved.

So what is happening here,map operator doesn't subscribe inner observable so it is returning promise object(Promise {}) and will not wait to be resolved,for that tap operator here logging this - response from map: Promise {}

Any higher order mapping(concatMap mergemap switchMap ExhaustMap) subscribe the inner observable.Know more: link1 | Link2

So in this concatMap(x => promise2) of code,concatMap will subscribe promise2 and will emit data when promise2 get resolved.

1st option using concatMap

from(promise1)
  .pipe(
    concatMap((x) => promise2),
    tap((x) => console.log('response from map: ', x))
    // other_rxjs_ops...
  )
  .subscribe();

In your case source observable which is from(promise1) will emit response once promise1 is resolved.After receiving response from promise1,concatMap will subscribe promise2

One Promise can emit only one data either resolve or reject so you can use mergeMap or any higher order map operator also here.

So you can consider 1st option if you need to use promise1 response to call promise2.

Now coming to your 2nd option,which is using concat.

So you can consider this over 1st option if promise2 is not dependent on response of promise1.

Simply your requirement is to call promise2 after completion of promise1

I am providing a working example,by which you can clear your doubts

import { concat, from, interval, of } from 'rxjs';
import {
  concatMap,
  exhaustMap,
  map,
  mergeMap,
  switchMap,
  take,
  tap,
} from 'rxjs/operators';

const source = interval(1000).pipe(take(5));

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise1 resolved!');
    // reject('Promise1 rejected!');
  }, 5000);
});

/*..............Option 1..............*/
source
  .pipe(
    mergeMap((result) => {
      console.log("Inside pipe: ",result);
      return promise1;
    })
    // concatMap(()=>promise1),
  )
  .subscribe({
    next: console.log,
    error: console.error,
    complete: () => console.log('Completed!'),
  });

// source.pipe(
//   tap(console.log),
//   map(()=>promise1),
// ).subscribe(console.log);

/*..............Option 2..............*/

concat(source, promise1).subscribe({
  next: console.log,
  error: console.error,
  complete: () => console.log('Completed!'),
});
Related