简体   繁体   English

反应器中的地图与平面地图

[英]map vs flatMap in reactor

I've found a lot of answers regarding RxJava , but I want to understand how it works in Reactor.我找到了很多关于RxJava的答案,但我想了解它在 Reactor 中的工作原理。

My current understanding is very vague, i tend to think of map as being synchronous and flatMap to be asynchronous but I can't really get my had around it.我目前的理解非常模糊,我倾向于认为 map 是同步的,而 flatMap 是异步的,但我无法真正理解它。

Here is an example:这是一个例子:

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

I have files (a Flux<FilePart> ) and i want to copy it to some UPLOAD_ROOT on the server.我有文件(一个Flux<FilePart> ),我想将它复制到服务器上的某个UPLOAD_ROOT中。

This example is taken from a book.这个例子摘自一本书。

I can change all the .map to .flatMap and vice versa and everything still works.我可以将所有.map更改为.flatMap ,反之亦然,一切仍然有效。 I wonder what the difference is.我想知道有什么区别。

  • map is for synchronous, non-blocking, 1-to-1 transformations map用于同步、非阻塞、一对一的转换
  • flatMap is for asynchronous (non-blocking) 1-to-N transformations flatMap用于异步(非阻塞)1 到 N 转换

The difference is visible in the method signature:差异在方法签名中可见:

  • map takes a Function<T, U> and returns a Flux<U> map接受一个Function<T, U>并返回一个Flux<U>
  • flatMap takes a Function<T, Publisher<V>> and returns a Flux<V> flatMap接受一个Function<T, Publisher<V>>并返回一个Flux<V>

That's the major hint: you can pass a Function<T, Publisher<V>> to a map , but it wouldn't know what to do with the Publishers , and that would result in a Flux<Publisher<V>> , a sequence of inert publishers.这是主要提示:您可以Function<T, Publisher<V>>传递给map ,但它不知道如何处理Publishers ,这将导致Flux<Publisher<V>> ,一个惰性发布者序列。

On the other hand, flatMap expects a Publisher<V> for each T .另一方面, flatMap期望每个T都有一个Publisher<V> It knows what to do with it: subscribe to it and propagate its elements in the output sequence.它知道如何处理它:订阅它并在输出序列中传播它的元素。 As a result, the return type is Flux<V> : flatMap will flatten each inner Publisher<V> into the output sequence of all the V s.结果,返回类型为Flux<V>flatMap会将每个内部Publisher<V>展平为所有V的输出序列。

About the 1-N aspect:关于1-N方面:

for each <T> input element, flatMap maps it to a Publisher<V> .对于每个<T>输入元素, flatMap将其映射到Publisher<V> In some cases (eg. an HTTP request), that publisher will emit only one item, in which case we're pretty close to an async map .在某些情况下(例如 HTTP 请求),该发布者只会发出一个项目,在这种情况下我们非常接近 async map

But that's the degenerate case.但这是退化的情况。 The generic case is that a Publisher can emit multiple elements, and flatMap works just as well.一般情况是Publisher可以发出多个元素,而flatMap也可以。

For an example, imagine you have a reactive database and you flatMap from a sequence of user IDs, with a request that returns a user's set of Badge .例如,假设您有一个响应式数据库,并且您从一系列用户 ID 中进行平面映射,请求返回用户的Badge集。 You end up with a single Flux<Badge> of all the badges of all these users.您最终得到所有这些用户的所有徽章的单个Flux<Badge>

Is map really synchronous and non-blocking ? map真的是同步和非阻塞的吗?

Yes: it is synchronous in the way the operator applies it (a simple method call, and then the operator emits the result) and non-blocking in the sense that the function itself shouldn't block the operator calling it.是的:它在运算符应用它的方式上是同步的(一个简单的方法调用,然后运算符发出结果)并且在函数本身不应该阻止调用它的运算符的意义上是非阻塞的。 In other terms it shouldn't introduce latency.换句话说,它不应该引入延迟。 That's because a Flux is still asynchronous as a whole.那是因为一个Flux作为一个整体还是异步的。 If it blocks mid-sequence, it will impact the rest of the Flux processing, or even other Flux .如果它阻塞中间序列,它将影响其余的Flux处理,甚至影响其他Flux

If your map function is blocking/introduces latency but cannot be converted to return a Publisher , consider publishOn / subscribeOn to offset that blocking work on a separate thread.如果您的 map 函数阻塞/引入延迟但无法转换为返回Publisher ,请考虑publishOn / subscribeOn以抵消单独线程上的阻塞工作。

The flatMap method is similar to the map method with the key difference that the supplier you provide to it should return a Mono<T> or Flux<T> . flatMap 方法类似于 map 方法,主要区别在于您提供给它的供应商应该返回Mono<T>Flux<T>

Using the map method would result in a Mono<Mono<T>> whereas using flatMap results in a Mono<T> .使用 map 方法会产生Mono<Mono<T>>而使用 flatMap 会产生Mono<T>

For example, it is useful when you have to make a network call to retrieve data, with a java API that returns a Mono, and then another network call that needs the result of the first one.例如,当您必须进行网络调用以检索数据时,使用返回 Mono 的 java API,然后另一个网络调用需要第一个结果的网络调用时,它很有用。

// 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

Also, it allows for handling errors precisely:此外,它允许精确地处理错误:

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

How map internally works in the Reactor. map 在 Reactor 内部是如何工作的。

MAP 的内部工作原理

Creating a Player class.创建一个Player类。

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

Now creating some instances of Player class现在创建Player类的一些实例

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();

What's important to understand about the map() is that the mapping is performed synchronously, as each item is published by the source Flux.了解 map() 的重要一点是映射是同步执行的,因为每个项目都由源 Flux 发布。 If you want to perform the mapping asynchronously, you should consider the flatMap() operation.如果要异步执行映射,则应考虑 flatMap() 操作。

How FlatMap internally works. FlatMap 内部是如何工作的。

FlatMap 内部是如何工作的。

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();

Internally in a Flatmap(), a map() operation is performed to the Mono to transform the String to Player.在 Flatmap() 内部,对 Mono 执行 map() 操作以将 String 转换为 Player。 Furthermore, subcribeOn () indicates that each subscription should take place in a parallel thread.此外, subcribeOn() 表示每个订阅都应该在并行线程中进行。 In absence of subscribeOn() flatmap() acts as a synchronized.在没有 subscribeOn() 的情况下,flatmap() 充当同步。

The map is for synchronous, non-blocking, one-to-one transformations while the flatMap is for asynchronous (non-blocking) One-to-Many transformations. map 用于同步、非阻塞、一对一转换,而 flatMap 用于异步(非阻塞)一对多转换。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM