web-dev-qa-db-fra.com

Comment fonctionne RxJS MergeMap?

Je ne comprends pas le but de mergeMap du tout. J'ai entendu deux "explications:

  1. "C'est comme SelectAll" dans LINQ - Nope.
  2. "Eh bien, c'est une combinaison de RxJS merge et map" - nope (ou je ne peux pas le répliquer).

Considérons le code suivant:

    var obs1 = new Rx.Observable.interval(1000);
    var obs2 = new Rx.Observable.interval(1000);

    //Just a merge and a map, works fine
    obs1.merge(obs2).map(x=> x+'a').subscribe(
      next => console.log(next)
    )

    //Who know what - seems to do the same thing as a plain map on 1 observable
    obs1.mergeMap(val => Rx.Observable.of(val + `B`))
        .subscribe(
          next => console.log(next)
        )

JS Bin

La dernière pièce intitulée "Qui sait quoi" ne fait rien de plus qu'une carte sur obs1 - à quoi ça sert?

Que fait réellement mergeMap? Qu'est-ce qu'un exemple de cas d'utilisation valide? (De préférence avec du code)

Articles qui ne m'ont pas du tout aidé (le code mergeMap ci-dessus est issu de l'un de ceux-ci): 1 , 2

44
VSO

tl; dr;mergeMap est bien plus puissant que map. Comprendre mergeMap est la condition nécessaire pour accéder à la pleine puissance de Rx.


similitudes

  • mergeMap et map agissent tous deux sur un seul flux (vs Zip, combineLatest)

  • mergeMap et map peuvent tous deux transformer des éléments d'un flux (vs filter, delay)

différences

carte

  • ne peut pas changer la taille du flux source (hypothèse: map elle-même ne throw); pour chaque élément de la source, exactement un mapped élément est émis; map ne peut pas ignorer les éléments (comme par exemple filter);

  • dans le cas du planificateur par défaut, la transformation se fait de manière synchrone; être clair à 100%: le flux source peut fournir ses éléments de manière asynchrone, mais chaque élément suivant est immédiatement mapped et réémis; map ne peut pas décaler des éléments dans le temps, comme par exemple delay

  • aucune restriction sur les valeurs de retour

  • id: x => x

fusionner la carte

  • peut changer la taille du flux source; pour chaque élément, il peut y avoir un nombre arbitraire (0, 1 ou plusieurs) de nouveaux éléments créés/émis

  • il offre un contrôle total sur l'asynchronicité - à la fois lorsque de nouveaux éléments sont créés/émis et combien d'éléments du flux source doivent être traités simultanément. par exemple, supposons que le flux source émette 10 éléments mais que maxConcurrency soit défini sur 2, les deux premiers éléments seront traités immédiatement et les 8 autres éléments mis en mémoire tampon; une fois que l'un des completed traités, le prochain élément du flux source sera traité, etc. - c'est un peu compliqué, mais regardez l'exemple ci-dessous

  • tous les autres opérateurs peuvent être implémentés avec seulement mergeMap et Observable constructeur

  • peut être utilisé pour des opérations asynchrones récursives

  • les valeurs de retour doivent être de type Observable (ou Rx doit savoir comment en créer une observable - par exemple, une promesse, un tableau)

  • id: x => Rx.Observable.of(x)

analogie de tableau

let array = [1,2,3]
fn             map                    mergeMap
x => x*x       [1,4,9]                error /*expects array as return value*/
x => [x,x*x]   [[1,1],[2,4],[3,9]]    [1,1,2,4,3,9]

L'analogie ne montre pas l'image complète et correspond en gros à .mergeMap Avec maxConcurrency défini sur 1. Dans un tel cas, les éléments seront ordonnés comme ci-dessus, mais dans le cas général cela ne doit pas nécessairement l'être alors. La seule garantie dont nous disposons est que l'émission de nouveaux éléments sera ordonnée en fonction de leur position dans le flux sous-jacent. Par exemple: [3,1,2,4,9,1] Et [2,3,1,1,9,4] Sont valides, mais [1,1,4,2,3,9] Ne l'est pas (puisque 4 A été émis après 2 Dans le flux sous-jacent) .

Quelques exemples utilisant mergeMap:

// implement .map with .mergeMap
Rx.Observable.prototype.mapWithMergeMap = function(mapFn) {
  return this.mergeMap(x => Rx.Observable.of(mapFn(x)));
}

Rx.Observable.range(1, 3)
  .mapWithMergeMap(x => x * x)
  .subscribe(x => console.log('mapWithMergeMap', x))

// implement .filter with .mergeMap
Rx.Observable.prototype.filterWithMergeMap = function(filterFn) {
  return this.mergeMap(x =>
    filterFn(x) ?
    Rx.Observable.of(x) :
    Rx.Observable.empty()); // return no element
}

Rx.Observable.range(1, 3)
  .filterWithMergeMap(x => x === 3)
  .subscribe(x => console.log('filterWithMergeMap', x))

// implement .delay with .mergeMap 
Rx.Observable.prototype.delayWithMergeMap = function(delayMs) {
  return this.mergeMap(x =>
    Rx.Observable.create(obs => {
      // setTimeout is naive - one should use scheduler instead
      const token = setTimeout(() => {
        obs.next(x);
        obs.complete();
      }, delayMs)
      return () => clearTimeout(token);
    }))
}

Rx.Observable.range(1, 3)
  .delayWithMergeMap(500)
  .take(2)
  .subscribe(x => console.log('delayWithMergeMap', x))

// recursive count
const count = (from, to, interval) => {
  if (from > to) return Rx.Observable.empty();
  return Rx.Observable.timer(interval)
    .mergeMap(() =>
      count(from + 1, to, interval)
      .startWith(from))
}

count(1, 3, 1000).subscribe(x => console.log('count', x))

// just an example of bit different implementation with no returns
const countMoreRxWay = (from, to, interval) =>
  Rx.Observable.if(
    () => from > to,
    Rx.Observable.empty(),
    Rx.Observable.timer(interval)
    .mergeMap(() => countMoreRxWay(from + 1, to, interval)
      .startWith(from)))

const maxConcurrencyExample = () =>
  Rx.Observable.range(1,7)
    .do(x => console.log('emitted', x))
    .mergeMap(x => Rx.Observable.timer(1000).mapTo(x), 2)
    .do(x => console.log('processed', x))
    .subscribe()

setTimeout(maxConcurrencyExample, 3100)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.1.1/Rx.min.js"></script>
100
artur grzesiak

.mergeMap() vous permet d'aplatir un observable d'ordre supérieur en un seul flux. Par exemple:

Rx.Observable.from([1,2,3,4])
  .map(i => getFreshApiData())
  .subscribe(val => console.log('regular map result: ' + val));

//vs

Rx.Observable.from([1,2,3,4])
  .mergeMap(i => getFreshApiData())
  .subscribe(val => console.log('mergeMap result: ' + val));

function getFreshApiData() {
  return Rx.Observable.of('retrieved new data')
    .delay(1000);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.1.0/Rx.js"></script>

Voir ma réponse à cette autre question pour une explication détaillée des opérateurs .xxxMap(): Rxjs - Comment puis-je extraire plusieurs valeurs à l'intérieur d'un tableau et les renvoyer au flux observable de manière synchrone =

20
Mark van Straten