map vs flatMap en reactor

8 minutos de lectura

avatar de usuario de shredding
trituración

Encontré muchas respuestas con respecto a RxJava, pero quiero entender cómo funciona en Reactor.

Mi comprensión actual es muy vaga, tiendo a pensar que el mapa es sincrónico y flatMap es asincrónico, pero realmente no puedo entenderlo.

Aquí hay un ejemplo:

files.flatMap { it ->
    Mono.just(Paths.get(UPLOAD_ROOT, it.filename()).toFile())
        .map {destFile ->
            destFile.createNewFile()
            destFile    
        }               
        .flatMap(it::transferTo)
}.then()  

tengo archivos (un Flux<FilePart>) y quiero copiarlo a algunos UPLOAD_ROOT en el servidor

Este ejemplo está tomado de un libro.

Puedo cambiar todo el .map a .flatMap y viceversa y todo sigue funcionando. Me pregunto cuál es la diferencia.

  • ¿Puede ser más específico acerca de su pregunta real? A qué métodos te refieres en realidad, hay múltiples map y flatMap métodos en Java.

    – Zabuzard

    5 de marzo de 2018 a las 16:43


  • Estoy hablando del proyecto Reactor. projectreactor.io/docs/core/release/api/reactor/core/publisher/…

    – trituración

    5 de marzo de 2018 a las 16:48

  • Sí, ahora por favor sea más específico sobre la pregunta. ¿Dónde exactamente tienes dificultades para entenderlo? ¿Por qué echar un vistazo al código fuente y la documentación no aclara la confusión? ¿Hay algo específico que no entiendes?

    – Zabuzard

    5 de marzo de 2018 a las 16:52


  • Entiendo por los documentos que ambas son formas de iterar sobre un Flux dado y que map es sincronizado y flatMap no es. Pero también entiendo que la función que doy en map se ejecuta asíncrono y no sé cuándo usar cuál.

    – trituración

    5 de marzo de 2018 a las 16:56

  • … Voy a actualizar con un ejemplo concreto.

    – trituración

    5 de marzo de 2018 a las 16:57

Avatar de usuario de Simon Baslé
simon basle

  • map es para transformaciones síncronas, sin bloqueo, 1 a 1
  • flatMap es para transformaciones asíncronas (sin bloqueo) de 1 a N

La diferencia es visible en la firma del método:

  • map toma una Function<T, U> y devuelve un Flux<U>
  • flatMap toma una Function<T, Publisher<V>> y devuelve un Flux<V>

Esa es la pista principal: tú poder pasar un Function<T, Publisher<V>> a un mappero no sabría qué hacer con el Publishersy eso daría como resultado una Flux<Publisher<V>>una secuencia de editores inertes.

Por otro lado, flatMap espera un Publisher<V> para cada T. Sabe qué hacer con él: suscribirse y propagar sus elementos en la secuencia de salida. Como resultado, el tipo de retorno es Flux<V>: flatMap aplanará cada interior Publisher<V> en la secuencia de salida de todo el Vs.

Sobre el aspecto 1-N:

para cada <T> elemento de entrada, flatMap lo asigna a un Publisher<V>. En algunos casos (por ejemplo, una solicitud HTTP), ese editor emitirá solo un elemento, en cuyo caso estamos bastante cerca de un asíncrono map.

Pero ese es el caso degenerado. El caso genérico es que un Publisher puede emitir múltiples elementos, y flatMap funciona igual de bien.

Por ejemplo, imagina que tienes una base de datos reactiva y usas FlatMap a partir de una secuencia de ID de usuario, con una solicitud que devuelve un conjunto de usuarios de Badge. Terminas con un solo Flux<Badge> de todas las insignias de todos estos usuarios.

Es map realmente sincronizado y sin bloqueo?

Sí: es síncrono en la forma en que el operador lo aplica (una llamada de método simple y luego el operador emite el resultado) y no bloquea en el sentido de que la función en sí no debería bloquear al operador que la llama. En otros términos, no debería introducir latencia. Eso es porque un Flux sigue siendo asincrónico en su conjunto. Si bloquea la mitad de la secuencia, afectará al resto de la Flux procesamiento, o incluso otros Flux.

Si su función de mapa está bloqueando/introduce latencia pero no se puede convertir para devolver un Publisherconsiderar publishOn/subscribeOn para compensar ese trabajo de bloqueo en un hilo separado.

  • quisiste decir mapa es blocking, ¿bien? yo tampoco entiendo el 1-to-N en realidad. ¿Puede dar un ejemplo cuando uno es útil sobre el otro? Entiendo que uso flatMap cuando espero que el resultado sea asíncrono, porque aplana a los editores una vez que el resultado está allí, ¿es correcto?

    – trituración

    8 de marzo de 2018 a las 10:31

  • no, la función de mapa no debe bloquear (a menos que también compense el trabajo en un hilo separado usando publishOn/subscribeOn). es decir, se ejecuta sincrónicamente pero no debe tener latencia. La función flatMap es asíncrona y, de hecho, el operador aplana los resultados a medida que están disponibles.

    – Simón Basle

    8 de marzo de 2018 a las 11:34

  • edité la respuesta para explicar estos dos aspectos + ejemplo flatMap 1-N

    – Simón Basle

    8 de marzo de 2018 a las 11:45

  • ¿Esta respuesta está desactualizada? Creo que lo que llamas flatMap ahora es “flatMapMany”, y flatMap hace algo diferente: github.com/reactor/reactor-core/issues/516

    – Hazel T.

    16 de noviembre de 2018 a las 22:07


  • Solo que Mono ahora tiene la sutileza adicional de tener flatMap (transformación asíncrona 1 a 1) y flatMapMany (asíncrono 1 a n)

    – Simón Basle

    17 de noviembre de 2018 a las 14:51

Avatar de usuario de Raouf Makhlouf
Raouf Makhlouf

El método flatMap es similar al método de mapa con la diferencia clave de que el proveedor que le proporcione debe devolver un Mono<T> o Flux<T>.

Usar el método del mapa daría como resultado una Mono<Mono<T>>
mientras que usar flatMap da como resultado un Mono<T>.

Por ejemplo, es útil cuando tienes que hacer una llamada de red para recuperar datos, con una API de Java que devuelve un Mono, y luego otra llamada de red que necesita el resultado de la primera.

// Signature of the HttpClient.get method
Mono<JsonObject> get(String url);

// The two urls to call
String firstUserUrl = "my-api/first-user";
String userDetailsUrl = "my-api/users/details/"; // needs the id at the end

// Example with map
Mono<Mono<JsonObject>> result = HttpClient.get(firstUserUrl).
  map(user -> HttpClient.get(userDetailsUrl + user.getId()));
// This results with a Mono<Mono<...>> because HttpClient.get(...)
// returns a Mono

// Same example with flatMap
Mono<JsonObject> bestResult = HttpClient.get(firstUserUrl).
  flatMap(user -> HttpClient.get(userDetailsUrl + user.getId()));
// Now the result has the type we expected

Además, permite manejar los errores con precisión:

public UserApi {
  
  private HttpClient httpClient;
    
  Mono<User> findUser(String username) {
    String queryUrl = "http://my-api-address/users/" + username;
    
    return Mono.fromCallable(() -> httpClient.get(queryUrl)).
      flatMap(response -> {
        if (response.statusCode == 404) return Mono.error(new NotFoundException("User " + username + " not found"));
        else if (response.statusCode == 500) return Mono.error(new InternalServerErrorException());
        else if (response.statusCode != 200) return Mono.error(new Exception("Unknown error calling my-api"));
        return Mono.just(response.data);
      });
  }
                                           
}

Avatar de usuario de Zahid Khan
Zahid Khan

Cómo funciona el mapa internamente en el Reactor.

Cómo funciona MAP internamente

Creando un Player clase.

@Data
@AllArgsConstructor
public class Player {
        String name;
        String name;
}

Ahora creando algunas instancias de Player clase

Flux<Player> players = Flux.just(
        "Zahid Khan",
        "Arif Khan",
        "Obaid Sheikh")
        .map(fullname -> {
            String[] split = fullname.split("\\s");
            return new Player(split[0], split[1]);
        });

StepVerifier.create(players)
          .expectNext(new Player("Zahid", "Khan"))
          .expectNext(new Player("Arif", "Khan"))
          .expectNext(new Player("Obaid", "Sheikh"))
          .verifyComplete();

Lo que es importante entender sobre map() es que el mapeo se realiza de forma síncrona, ya que Flux de origen publica cada elemento. Si desea realizar la asignación de forma asíncrona, debe considerar la operación flatMap().

Cómo funciona FlatMap internamente.

Cómo funciona FlatMap internamente.

Flux<Player> players = Flux.just(
      "Zahid Khan", 
      "Arif Khan", 
      "Obaid Sheikh")
      .flatMap(
            fullname -> 
                  Mono.just(fullname).map(p -> {
                        String[] split = p.split("\\s");
                        return new Player(split[0], split[1]);
        }).subscribeOn(Scheduler.parallel()));

        List<Player> playerList = Arrays.asList(
                  new Player("Zahid", "Khan"),
                  new Player("Arif", "Khan"), 
                  new Player("Obaid", "Sheikh"));

        StepVerifier.create(players).expectNextMatches(player ->         
                playerList.contains(player))    
                        .expectNextMatches(player ->  
                                playerList.contains(player))
                        .expectNextMatches(player -> 
                                playerList.contains(player))
                        .expectNextMatches(player -> 
                                playerList.contains(player))
                        .verifyComplete();

Internamente, en Flatmap(), se realiza una operación map() en Mono para transformar String en Player. Además, subcribeOn () indica que cada suscripción debe realizarse en un hilo paralelo. En ausencia de subscribeOn() flatmap() actúa como sincronizado.

El mapa es para transformaciones uno a uno síncronas, sin bloqueo, mientras que flatMap es para transformaciones uno a muchos asíncronas (sin bloqueo).

  • Basado en la imagen En su ejemplo de mapa plano, es posible tener una cantidad diferente de elementos de entrada y salida. ¿Podría dar un ejemplo? porque en su ejemplo tiene la misma cantidad (3 elementos de entrada y 3 elementos de salida).

    – desbordamiento de gstack

    10 de enero a las 8:10

¿Ha sido útil esta solución?