¿Por qué necesitamos usar flatMap?

92

Estoy empezando a usar RxJS y no entiendo por qué en este ejemplo necesitamos usar una función como flatMapo concatAll; ¿Dónde está la matriz de matrices aquí?

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

Si alguien puede explicar visualmente lo que está sucediendo, será muy útil.

usuario233232
fuente
1
esta respuesta es excelente debido a las valiosas referencias que proporciona, pero la terminología de rxjs no se traduce bien al inglés. (las imágenes son mejores). Es por eso que recomiendo en su lugar ejecutar ejemplos simples como este, o ejemplos más complejos en el repositorio rxjs y agregar operadores ".do" antes y después de un mapa plano y un operador de mapa, y luego simplemente establecer un punto de interrupción con el depurador de Chrome. verá instantáneamente que cada uno produce una salida diferente
HipsterZipster
5
Creo que si flatMaphubiera sido nombrado mapThenFlatten, sería menos confuso.
Cabra

Respuestas:

73

Cuando comencé a echar un vistazo Rxjstambién tropecé con esa piedra. Lo que me ayudó fue lo siguiente:

  • documentación de reactivex.io. Por ejemplo, para flatMap: http://reactivex.io/documentation/operators/flatmap.html
  • documentación de rxmarbles: http://rxmarbles.com/ . No encontrará flatMapallí, debe buscar en su mergeMaplugar (otro nombre).
  • la introducción a Rx que se ha perdido: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 . Aborda un ejemplo muy similar. En particular, aborda el hecho de que una promesa es similar a un observable que emite un solo valor.
  • finalmente mirando la información de tipo de RxJava. Javascript no escrito no ayuda aquí. Básicamente, si Observable<T>denota un objeto observable que empuja valores de tipo T, entonces flatMaptoma una función de tipo T' -> Observable<T>como argumento y devuelve Observable<T>. maptoma una función de tipo T' -> Ty devuelve Observable<T>.

    Volviendo a su ejemplo, tiene una función que produce promesas a partir de una cadena de URL. Entonces T' : string, y T : promise. Y por lo que dijimos antes promise : Observable<T''>, entonces T : Observable<T''>, con T'' : html. Si pones esa función de producción de promesas map, obtienes Observable<Observable<T''>>cuando lo que quieres es Observable<T''>: quieres que el observable emita los htmlvalores. flatMapse llama así porque aplana (elimina una capa observable) del resultado map. Dependiendo de su experiencia, esto podría ser chino para usted, pero todo se volvió muy claro para mí al escribir información y el dibujo de aquí: http://reactivex.io/documentation/operators/flatmap.html .

usuario3743222
fuente
2
Me olvidé de mencionar que usted debería ser capaz de simplificar return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));al return jQuery.getJSON(requestUrl);que flatMaptambién acepta una función del selector que devuelve una función promesa es decir, del tipo T' -> Promise.
user3743222
2
Vaya, ese GitHub Gist ( gist.github.com/staltz/868e7e9bc2a7b8c1f754 ) es increíblemente fantástico. Se lo recomiendo a cualquiera que trabaje con bibliotecas ReactiveX como RxJS.
Jacob Stamm
@JacobStamm estoy de acuerdo. Simplemente facilita las cosas.
CruelEngine
¿Qué significa esta sintaxis T’ -> T:? Entiendo el Tcomo genérico, pero ¿qué es el apóstrofe y la flecha sin grasa?
1252748
Puede reemplazar T 'por X o Y sin cambiar el significado en ninguna parte de la respuesta. La flecha es la notación Haskell para la firma tipográfica. Entonces T '-> T es la firma de una función que toma un elemento de tipo T' y devuelve un elemento de 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]]

Utiliza flatMap cuando tiene un Observable cuyos resultados son más Observables.

Si tiene un observable que es producido por otro observable, no puede filtrarlo, reducirlo o mapearlo directamente porque tiene un Observable, no los datos. Si produce un observable, elija flatMap sobre el mapa; entonces estás bien.

Como en el segundo fragmento, si está realizando una operación asincrónica, debe usar 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
fuente
31

flatMap transformar los elementos emitidos por un Observable en nuevos Observables, luego aplana las emisiones de esos en un Observable único.

Vea el escenario a continuación donde get("posts")devuelve un Observable que está "aplanado" por 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
fuente
2
Bonita y sencilla respuesta. Creo que este podría ser el mejor.
vaughan
"flatMap transforma los elementos emitidos por un Observable en nuevos Observables, luego aplana las emisiones de esos en un Observable único". Esto es excelente.
MBak
31

La gente tiende a complicar demasiado las cosas al dar la definición que dice:

flatMap transforma los elementos emitidos por un Observable en Observables, luego aplana las emisiones de esos en un solo Observable

Juro que esta definición todavía me confunde, pero la voy a explicar de la manera más simple que es usando un ejemplo.

Nuestra situación : tenemos un observable que devuelve datos (URL simple) que vamos a usar para hacer una llamada HTTP que devolverá un observable que contiene los datos que necesitamos para que puedas visualizar la situación así:

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

como puede ver, no podemos acceder a los datos que necesitamos directamente, por lo que la primera forma de recuperar los datos podemos usar solo suscripciones normales como esta:

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

esto funciona, pero como puede ver, tenemos que anidar suscripciones para obtener nuestros datos, esto actualmente no se ve mal, pero imagine que tenemos 10 suscripciones anidadas que no se pueden mantener.

así que una mejor manera de manejar esto es simplemente usar el operador flatMapque hará lo mismo pero nos hace evitar esa suscripción anidada:

Observable_1
    .flatMap(URL => Http.get(URL))
    .subscribe(Data_We_Need => console.log(Data_We_Need));
Hamed Baatour
fuente
19

Sencillo:

[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
fuente
16

No es una matriz de matrices. Es un observable de observable (s).

Lo siguiente devuelve un flujo de cadena observable.

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

Si bien esto devuelve un flujo observable de flujo observable de json

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

flatMap aplana lo observable automáticamente para que podamos observar la secuencia json directamente

Lucius
fuente
3
Es difícil entender este concepto, ¿puede agregar comentarios a visual lo que quiere decir "devuelve un flujo observable de flujo observable de json". Gracias.
user233232
@ user233232, como [x, x, x, x] a [[xxx], [[xxx], [xxx]]]
serkan
La clave para comprender la primera oración es comprender que flatMap(y map) no son especiales para las matrices. Es posible definir estas operaciones en cualquier contenedor o envoltorio genérico, incluyendo matrices, diccionarios, "opcionales", flujos reactivos, promesas, punteros e incluso funciones mismas. Ésta es una propiedad emergente de una construcción matemática llamada mónada. Todos los ejemplos anteriores cumplen los requisitos para ser una mónada, por lo que a todos se les puede dar una definición de mapy a flatMap(con algunas salvedades).
mklbtz
14

Aquí para mostrar la implementación equivalente de un flatMap usando suscripciones.

Sin 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

Espero que pueda ayudar.

Olivier.

olivier cherrier
fuente
13

Un Observable es un objeto que emite una secuencia de eventos: Siguiente, Error y Completado.

Cuando su función devuelve un Observable, no devuelve un flujo, sino una instancia de Observable. El flatMapoperador simplemente asigna esa instancia a una secuencia.

Ese es el comportamiento de flatMapcuando se compara con map: Ejecutar la función dada y aplanar el objeto resultante en una secuencia.

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

Sin 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
fuente
0

flatMap transforma los elementos emitidos por un Observable en Observables, luego aplana las emisiones de esos en un solo Observable

No soy estúpido, pero tuve que leer esto 10 veces y todavía no lo entiendo. Cuando leo el fragmento de código:

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

entonces pude entender lo que está pasando, hace dos cosas:

flatMap :

  1. map : transform *) elementos emitidos en Observables.
  2. plano : luego fusiona esos Observables como uno Observable.

*) La palabra transformar dice que el elemento se puede transformar en otra cosa.

Luego, el operador de combinación se vuelve claro, realiza el aplanamiento sin el mapeo. ¿Por qué no llamarlo mergeMap ? Parece que también hay un Alias mergeMap con ese nombre para flatMap .

Herman Van Der Blom
fuente