关于React中的Java:Map vs FlatMap

map vs flatMap in reactor

我已经找到了很多有关RxJava的答案,但是我想了解它在Reactor中的工作方式。

我目前的理解非常模糊,我倾向于认为map是同步的,而flatMap是异步的,但是我真的无法解决它。

这里是一个例子:

1
2
3
4
5
6
7
8
files.flatMap { it ->
    Mono.just(Paths.get(UPLOAD_ROOT, it.filename()).toFile())
        .map {destFile ->
            destFile.createNewFile()
            destFile    
        }              
        .flatMap(it::transferTo)
}.then()

我有文件(a Flux<FilePart>),我想将其复制到服务器上的某些UPLOAD_ROOT

此示例摘自一本书。

我可以将所有.map更改为.flatMap,反之亦然,一切仍然有效。我不知道有什么区别。


  • map用于同步,非阻塞,一对一转换
  • flatMap用于异步(非阻塞)的1到N转换

区别在方法签名中可见:

  • map接受Function<T, U>并返回Flux<U>
  • flatMap接受Function<T, Publisher<V>>并返回Flux<V>

这是主要提示:您可以将Function<T, Publisher<V>>传递给map,但是它不知道如何处理Publishers,这将导致产生Flux<Publisher<V>>(惰性序列)发布者。

另一方面,flatMap期望每个T都有一个Publisher<V>。它知道如何处理它:订阅它并在输出序列中传播它的元素。结果,返回类型为Flux<V>flatMap将把每个内部Publisher<V>展平为所有V的输出序列。

关于1-N方面:

对于每个<T>输入元素,

flatMap映射到Publisher<V>。在某些情况下(例如HTTP请求),该发布者将仅发出一项,在这种情况下,我们非常接近异步map

但这就是简朴的情况。一般情况是Publisher可以发出多个元素,而flatMap也可以工作。

例如,假设您有一个反应性数据库,并且您从一系列用户ID中获取了flatMap,并且请求返回了一个用户集Badge。最后,所有这些用户的所有徽章中只有一个Flux<Badge>

map确实是同步且非阻塞的吗?

是:它在操作员应用它的方式(一个简单的方法调用,然后操作员发出结果)上是同步的,并且在函数本身不应该阻止操作员调用它的意义上说是非阻塞的。换句话说,它不应该引入延迟。这是因为Flux总体上还是异步的。如果阻塞中间序列,将影响其余的Flux处理,甚至其他Flux

如果您的map函数正在阻止/引入了延迟,但无法转换为返回Publisher,请考虑使用publishOn / subscribeOn来抵消在单独线程上的阻止工作。


flatMap方法与map方法类似,不同之处在于您提供的供应商应返回Mono<T>Flux<T>

使用map方法将导致Mono<Mono<T>>
而使用flatMap会导致Mono<T>

例如,当您必须进行网络调用以检索数据时,使用返回一个Mono的java API,然后进行另一个需要第一个结果的网络调用的实用程序非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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

此外,它还允许精确地处理错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
      });
  }
                                           
}