Perché dobbiamo usare flatMap?


92

Sto iniziando a usare RxJS e non capisco perché in questo esempio dobbiamo usare una funzione come flatMapo concatAll; dov'è l'array di array qui?

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(url => {console.log(url)})

Se qualcuno può spiegare visivamente cosa sta succedendo, sarà molto utile.


1
questa risposta è ottima a causa dei preziosi riferimenti che fornisce, ma la terminologia rxjs non si traduce bene in inglese. (le foto sono migliori). Ecco perché consiglio invece di eseguire esempi semplici come questo, o esempi più complessi nel repository rxjs e aggiungere operatori ".do" prima e dopo un flatmap e un operatore mappa, quindi impostare semplicemente un punto di interruzione con il debugger di Chrome. vedrai immediatamente che ognuno produce un output diverso
HipsterZipster,

5
Penso che se flatMapfosse stato nominato mapThenFlatten, sarebbe stato meno confuso.
capra

Risposte:


73

Quando ho iniziato a dare un'occhiata Rxjssono inciampato anche su quella pietra. Ciò che mi ha aiutato è il seguente:

  • documentazione da reattivex.io. Ad esempio, per flatMap: http://reactivex.io/documentation/operators/flatmap.html
  • documentazione da rxmarbles: http://rxmarbles.com/ . Non troverai flatMaplì, devi guardare mergeMapinvece (un altro nome).
  • l'introduzione a Rx che ti sei perso: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 . Affronta un esempio molto simile. In particolare affronta il fatto che una promessa è simile a un osservabile che emette un solo valore.
  • finalmente guardando le informazioni sul tipo da RxJava. Javascript non essere digitato non aiuta qui. Fondamentalmente se Observable<T>denota un oggetto osservabile che spinge valori di tipo T, flatMapaccetta una funzione di tipo T' -> Observable<T>come argomento e restituisce Observable<T>. mapaccetta una funzione di tipo T' -> Te restituisce Observable<T>.

    Tornando al tuo esempio, hai una funzione che produce promesse da una stringa di URL. Quindi T' : stringe T : promise. E da quello che abbiamo detto prima promise : Observable<T''>, quindi T : Observable<T''>, con T'' : html. Se inserisci quella funzione che produce promesse map, ottieni Observable<Observable<T''>>quando ciò che vuoi è Observable<T''>: vuoi che l'osservabile emetta i htmlvalori. flatMapè chiamato così perché appiattisce (rimuove uno strato osservabile) il risultato da map. A seconda del tuo background, questo potrebbe essere cinese per te, ma tutto è diventato chiarissimo per me con le informazioni sulla digitazione e il disegno da qui: http://reactivex.io/documentation/operators/flatmap.html .


2
Ho dimenticato di dire che dovresti essere in grado di semplificare return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));in return jQuery.getJSON(requestUrl);quanto flatMapaccetta anche una funzione di selettore che restituisce una promessa cioè una funzione di tipo T' -> Promise.
user3743222

2
Wow, quel GitHub Gist ( gist.github.com/staltz/868e7e9bc2a7b8c1f754 ) è dannatamente fantastico. Lo consiglio a chiunque lavori con qualsiasi libreria ReactiveX come RxJS.
Jacob Stamm

@JacobStamm sono d'accordo. Rende le cose più semplici.
CruelEngine

Che cosa significa questa sintassi media: T’ -> T? Capisco il Tcome generico, ma cos'è l'apostrofo e la freccia non grassa?
1252748

Puoi sostituire T 'con X o Y senza cambiare il significato in nessun punto della risposta. La freccia è la notazione Haskell per la firma del tipo. Quindi T '-> T è la firma di una funzione che accetta un elemento di tipo T' e restituisce un elemento di tipo T
user3743222

124
['a','b','c'].flatMap(function(e) {
    return [e, e+ 'x', e+ 'y',  e+ 'z'  ];
});
//['a', 'ax', 'ay', 'az', 'b', 'bx', 'by', 'bz', 'c', 'cx', 'cy', 'cz']


['a','b','c'].map(function(e) {
    return [e, e+ 'x', e+ 'y',  e+ 'z'  ];
});
//[Array[4], Array[4], Array[4]]

Usi flatMap quando hai un Observable i cui risultati sono più osservabili.

Se hai un osservabile prodotto da un altro osservabile non puoi filtrarlo, ridurlo o mapparlo direttamente perché hai un osservabile e non i dati. Se produci un osservabile scegli flatMap su map; allora stai bene.

Come nel secondo frammento, se stai eseguendo un'operazione asincrona devi usare flatMap.

var source = Rx.Observable.interval(100).take(10).map(function(num){
    return num+1
});
source.subscribe(function(e){
    console.log(e)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>

var source = Rx.Observable.interval(100).take(10).flatMap(function(num){
    return Rx.Observable.timer(100).map(() => num)
});
source.subscribe(function(e){
    console.log(e)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>


31

flatMap trasforma gli elementi emessi da un osservabile in nuovi osservabili, quindi appiattisce le emissioni di quelli in un singolo osservabile.

Controlla lo scenario seguente in cui get("posts")restituisce un osservabile "appiattito" da flatMap.

myObservable.map(e => get("posts")).subscribe(o => console.log(o));
// this would log Observable objects to console.  

myObservable.flatMap(e => get("posts")).subscribe(o => console.log(o));
// this would log posts to console.

2
Bella, semplice risposta. Penso che questo potrebbe essere il migliore.
vaughan

"flatMap trasforma gli elementi emessi da un osservabile in nuovi osservabili, quindi appiattisce le emissioni da quelli in un singolo osservabile." Questa è roba eccellente.
MBak

31

Le persone tendono a complicare eccessivamente le cose dando la definizione che dice:

flatMap trasforma gli elementi emessi da un Observable in Observables, quindi appiattisce le emissioni da quelli in un unico Observable

Giuro che questa definizione mi confonde ancora ma la spiegherò nel modo più semplice che è usando un esempio

La nostra situazione : abbiamo un osservabile che restituisce dati (URL semplice) che useremo per effettuare una chiamata HTTP che restituirà un osservabile contenente i dati di cui abbiamo bisogno in modo da poter visualizzare la situazione in questo modo:

Observable 1
    |_
       Make Http Call Using Observable 1 Data (returns Observable_2)
            |_
               The Data We Need

così come puoi vedere non possiamo raggiungere direttamente i dati di cui abbiamo bisogno, quindi il primo modo per recuperare i dati possiamo usare solo normali abbonamenti come questo:

Observable_1.subscribe((URL) => {
         Http.get(URL).subscribe((Data_We_Need) => {
                  console.log(Data_We_Need);
          });
});

funziona ma, come puoi vedere, dobbiamo annidare gli abbonamenti per ottenere i nostri dati, al momento non sembra male ma immagina di avere 10 abbonamenti annidati che diventerebbero non gestibili.

quindi un modo migliore per gestirlo è semplicemente usare l'operatore flatMapche farà la stessa cosa ma ci fa evitare quell'abbonamento annidato:

Observable_1
    .flatMap(URL => Http.get(URL))
    .subscribe(Data_We_Need => console.log(Data_We_Need));

19

Semplice:

[1,2,3].map(x => [x, x * 10])
// [[1, 10], [2, 20], [3, 30]]

[1,2,3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]]

16

Non è un array di array. È un osservabile di osservabili.

Quanto segue restituisce un flusso osservabile di stringa.

requestStream
  .map(function(requestUrl) {
    return requestUrl;
  });

Mentre questo restituisce un flusso osservabile di flusso osservabile di json

requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

flatMap appiattisce automaticamente l'osservabile per noi in modo che possiamo osservare direttamente il flusso json


3
È difficile capire questo concetto, puoi per favore aggiungere commenti a visuale cosa intendi "restituisce un flusso osservabile di flusso osservabile di json". Grazie.
user233232

@ user233232, come da [x, x, x, x] a [[xxx], [[xxx], [xxx]]]
serkan

La chiave per comprendere la prima frase è capire che flatMap(e map) non sono speciali per gli array. È possibile definire queste operazioni su qualsiasi contenitore o wrapper generico, inclusi array, dizionari, "optionals", flussi reattivi, promesse, puntatori e persino funzioni stesse. Questa è una proprietà emergente di un costrutto matematico chiamato monade. Tutti gli esempi sopra soddisfano i requisiti per essere una monade, quindi a tutti può essere data una definizione di mape a flatMap(con alcuni avvertimenti).
mklbtz

14

Qui per mostrare l'implementazione equivalente di una flatMap utilizzando gli abbonamenti.

Senza flatMap:

this.searchField.valueChanges.debounceTime(400)
.subscribe(
  term => this.searchService.search(term)
  .subscribe( results => {
      console.log(results);  
      this.result = results;
    }
  );
);

Con flatMap:

this.searchField.valueChanges.debounceTime(400)
    .flatMap(term => this.searchService.search(term))
    .subscribe(results => {
      console.log(results);
      this.result = results;
    });

http://plnkr.co/edit/BHGmEcdS5eQGX703eRRE?p=preview

Spero possa aiutare.

Olivier.


13

Un Observable è un oggetto che emette un flusso di eventi: Next, Error e Completed.

Quando la tua funzione restituisce un Observable, non restituisce un flusso, ma un'istanza di Observable. IlflatMap operatore associa semplicemente quell'istanza a un flusso.

Questo è il comportamento di flatMapquando confrontato con map: Eseguire la funzione data e appiattire l'oggetto risultante in un flusso.


7

Con flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(json => {console.log(json)})

Senza flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(jsonStream => {
  jsonStream.subscribe(json => {console.log(json)})
})

0

flatMap trasforma gli elementi emessi da un osservabile in osservabili, quindi appiattisce le emissioni da quelli in un singolo osservabile

Non sono stupido ma ho dovuto leggerlo 10 volte e ancora non lo capisco. Quando ho letto lo snippet di codice:

[1,2,3].map(x => [x, x * 10])
// [[1, 10], [2, 20], [3, 30]]

[1,2,3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]

poi ho potuto capire cosa sta succedendo, fa due cose:

flatMap :

  1. map : transform *) oggetti emessi in osservabili.
  2. flat : quindi unisci quegli osservabili come un osservabile.

*) La parola di trasformazione dice che l'oggetto può essere trasformato in qualcos'altro.

Quindi l' operatore di unione diventa chiaro, esegue l'appiattimento senza la mappatura. Perché non chiamarlo mergeMap ? Sembra che ci sia anche un Alias mergeMap con quel nome per flatMap .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.