Dlaczego musimy używać flatMap?

95

Zaczynam używać RxJS i nie rozumiem, dlaczego w tym przykładzie musimy użyć funkcji takiej jak flatMaplub concatAll; gdzie jest tablica tablic?

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)})

Jeśli ktoś może wizualnie wyjaśnić, co się dzieje, będzie to bardzo pomocne.

user233232
źródło
1
ta odpowiedź jest świetna ze względu na cenne odniesienia, które zawiera, ale terminologia rxjs nie przekłada się dobrze na angielski. (zdjęcia są lepsze). Dlatego zalecam zamiast tego uruchamianie prostych przykładów, takich jak ten, lub bardziej złożonych przykładów w repozytorium rxjs i dodawanie operatorów „.do” przed i po operatorze mapy płaskiej i mapie, a następnie po prostu ustawienie punktu przerwania w debugerze Chrome. natychmiast zobaczysz, że każdy z nich generuje inny wynik
HipsterZipster
5
Myślę, że gdyby flatMapzostał nazwany mapThenFlatten, byłoby mniej zagmatwane.
koza

Odpowiedzi:

73

Kiedy zacząłem się przyglądać, Rxjsnatknąłem się też na ten kamień. Pomogło mi to:

  • dokumentacja z reactivex.io. Na przykład dla flatMap: http://reactivex.io/documentation/operators/flatmap.html
  • dokumentacja z rxmarbles: http://rxmarbles.com/ . Nie znajdziesz flatMaptam, musisz mergeMapzamiast tego zajrzeć (inna nazwa).
  • wprowadzenie do Rx, którego brakowało: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 . Dotyczy bardzo podobnego przykładu. W szczególności odnosi się do faktu, że obietnica jest podobna do obserwowalnego emitowania tylko jednej wartości.
  • wreszcie patrząc na informacje o typie z RxJava. Brak wpisywania kodu JavaScript nie pomoże tutaj. Zasadniczo, jeśli Observable<T>oznacza obserwowalny obiekt, który wypycha wartości typu T, to flatMapprzyjmuje funkcję typu T' -> Observable<T>jako argument i zwraca Observable<T>. mapprzyjmuje funkcję typu T' -> Ti zwraca Observable<T>.

    Wracając do twojego przykładu, masz funkcję, która generuje obietnice z ciągu adresu URL. Więc T' : stringi T : promise. Z tego, co powiedzieliśmy wcześniej promise : Observable<T''>, więc T : Observable<T''>z T'' : html. Jeśli umieścisz tę obietnicę produkującą funkcję map, otrzymasz Observable<Observable<T''>>to, czego chcesz Observable<T''>: chcesz, aby obserwowalne emitowały htmlwartości. flatMapnazywa się tak, ponieważ spłaszcza (usuwa obserwowalną warstwę) wynik z map. W zależności od Twojego doświadczenia może to być dla Ciebie język chiński, ale wszystko stało się dla mnie jasne dzięki wpisywaniu informacji i rysowaniu stąd: http://reactivex.io/documentation/operators/flatmap.html .

user3743222
źródło
2
Zapomniałem wspomnieć, że powinieneś być w stanie uprościć return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));do return jQuery.getJSON(requestUrl);jak flatMapprzyjmuje również funkcję selektora która zwraca obietnica czyli funkcję typu T' -> Promise.
user3743222
2
Wow, ten GitHub Gist ( gist.github.com/staltz/868e7e9bc2a7b8c1f754 ) jest cholernie fantastyczny. Polecam każdemu, kto pracuje z dowolnymi bibliotekami ReactiveX, takimi jak RxJS.
Jacob Stamm
@JacobStamm zgadzam się. Po prostu ułatwia sprawę.
CruelEngine
Co to oznacza składni: T’ -> T? Rozumiem Tjako rodzaj, ale co to jest apostrof i nietłusta strzała?
1252748
Możesz zamienić T 'na X lub Y bez zmiany znaczenia w dowolnym miejscu odpowiedzi. Strzałka to notacja Haskell dla podpisu typu. Więc T '-> T jest sygnaturą funkcji, która przyjmuje element typu T' i zwraca element typu 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]]

Używasz flatMap, gdy masz Observable, którego wyniki są bardziej Observables.

Jeśli masz obserwowalne, które są produkowane przez inne obserwowalne, nie możesz ich filtrować, redukować ani mapować bezpośrednio, ponieważ masz Observable, a nie dane. Jeśli utworzysz obserwowalne, wybierz flatMap over map; to wszystko w porządku.

Podobnie jak w drugim fragmencie, jeśli wykonujesz operację asynchroniczną, musisz użyć 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>

serkan
źródło
35

Ludzie mają tendencję do nadmiernego komplikowania rzeczy , podając definicję, która mówi:

flatMap przekształca elementy emitowane przez Observable w Observable, a następnie spłaszcza emisje z tych do jednego Observable

Przysięgam, że ta definicja wciąż mnie dezorientuje, ale wyjaśnię to w najprostszy sposób, czyli na przykładzie

Nasza sytuacja : mamy obserwowalną, która zwraca dane (prosty adres URL), których zamierzamy użyć do wykonania wywołania HTTP, które zwróci obserwowalne zawierające potrzebne nam dane, abyś mógł zwizualizować sytuację w następujący sposób:

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

więc jak widać, nie możemy bezpośrednio dotrzeć do potrzebnych nam danych, więc pierwszym sposobem na odzyskanie danych możemy użyć zwykłych subskrypcji, takich jak ta:

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

to działa, ale jak widać, musimy zagnieżdżać subskrypcje, aby uzyskać nasze dane, obecnie nie wygląda to źle, ale wyobraź sobie, że mamy 10 zagnieżdżonych subskrypcji, które stałyby się nie do utrzymania.

więc lepszym sposobem rozwiązania tego problemu jest użycie operatora, flatMapktóry zrobi to samo, ale pozwoli nam uniknąć tej zagnieżdżonej subskrypcji:

Observable_1
    .flatMap(URL => Http.get(URL))
    .subscribe(Data_We_Need => console.log(Data_We_Need));
Hamed Baatour
źródło
32

flatMap przekształcić elementy emitowane przez Observable w nowe Observable, a następnie spłaszcza emisje z tych do jednego Observable.

Sprawdź poniższy scenariusz, w którym get("posts")zwraca Observable, który jest „spłaszczany” przez 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.
Emmanuel Osimosu
źródło
2
Ładna, prosta odpowiedź. Myślę, że to może być najlepsze.
vaughan
„flatMap przekształca elementy emitowane przez Observable w nowe Observable, a następnie spłaszcza emisje z tych do jednego Observable”. To doskonała rzecz.
MBak
19

Prosty:

[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]]
drpicox
źródło
16

To nie jest tablica tablic. To jest obserwowalne z obserwowalnymi.

Poniższe zwraca obserwowalny strumień ciągu.

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

Chociaż zwraca to obserwowalny strumień obserwowalnego strumienia json

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

flatMap automatycznie spłaszcza obserwowalne dla nas, dzięki czemu możemy bezpośrednio obserwować strumień json

Lucjusz
źródło
3
Trudno zrozumieć tę koncepcję, czy możesz dodać komentarze do wizualnych, co masz na myśli, „zwraca obserwowalny strumień obserwowalnego strumienia json”. dzięki.
user233232
@ user233232, na przykład [x, x, x, x] do [[xxx], [[xxx], [xxx]]]
serkan
Kluczem do zrozumienia pierwszego zdania jest zrozumienie, że flatMap(i map) nie są specjalne dla tablic. Możliwe jest zdefiniowanie tych operacji na dowolnym ogólnym pojemniku lub opakowaniu, w tym na tablicach, słownikach, „opcjach”, strumieniach reaktywnych, obietnicach, wskaźnikach, a nawet samych funkcjach. Jest to wyłaniająca się właściwość konstrukcji matematycznej zwanej monadą. Wszystkie powyższe przykłady spełniają wymagania bycia monadą, więc wszystkim można nadać definicję mapi a flatMap(z pewnymi zastrzeżeniami).
mklbtz,
14

Tutaj, aby pokazać równoważną implementację flatMap przy użyciu subskrybentów.

Bez flatMap:

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

Z 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

Mam nadzieję, że to pomoże.

Olivier.

olivier cherrier
źródło
13

Observable to obiekt, który emituje strumień zdarzeń: Next, Error i Completed.

Gdy funkcja zwraca Observable, nie zwraca strumienia, ale instancję Observable. flatMapOperator po prostu odwzorowuje to wystąpienie do strumienia.

To jest zachowanie w flatMapporównaniu z map: Wykonaj daną funkcję i spłaszcz wynikowy obiekt w strumień.

AkkarinZA
źródło
7

Z 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)})

Bez 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)})
})
Thanawat
źródło
0

flatMap przekształca elementy emitowane przez Observable w Observable, a następnie spłaszcza emisje z tych do jednego Observable

Nie jestem głupi, ale musiałem to przeczytać 10 razy i nadal nie rozumiem. Kiedy czytam fragment kodu:

[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]

wtedy mógłbym zrozumieć, co się dzieje, robi dwie rzeczy:

flatMap :

  1. map : transform *) wyemitowane elementy do Observables.
  2. flat : następnie połącz te Observables w jedną Observable.

*) Słowo transformacji mówi, że element można przekształcić w coś innego.

Następnie operator scalania staje się jasny, wykonuje spłaszczanie bez mapowania. Dlaczego nie nazwać go mergeMap ? Wygląda na to, że istnieje również alias mergeMap o tej nazwie dla flatMap .

Herman Van Der Blom
źródło