Vert.x 是一个异步框架。因此,它需要一种方法来表示可能尚未准备好但将来可用的值,也称为延迟值(deferred values)。您可能熟悉不同名称的延迟值:Promise, Future, Deferred, Mono, Uni 都是延迟值设计模式的实现。
Vert.x 有自己的延迟值实现,简称为 Future ,是Vert.x的核心概念之一。不过,这不应该与Java的 Future 混淆。Vert.x的Future实现是完全独立的。
💡提示: 尽管 Vert.x
Future可以使用toCompletionStage()方法转换为 Java的Future。
Vert.x Future 有两个描述非常相似且可能会让您感到困惑的方法: compose 和 map 。
我想深入研究它们中的每一个是如何工作的,以及何时应该使用它们。
让我们从最简单的场景开始,看看一个完整的future:
Future<String> f = Future.succeededFuture("a");
此处, f 表示已成功计算的延迟值。
现在,让我们看看 compose() 和 map() 方法在延迟值上的行为,如果我们向它们传递一个简单的函数:给定 X,返回 X
Future<String> composeResult = f.compose(a -> Future.succeededFuture(a));
Future<String> mapResult = f.map(a -> a);
虽然 map() 允许我们按原样返回值,但 compose() 强制我们将结果包装在新的 Future 中。这是因为 map() 可以返回任何类型的结果,而 compose() 必须返回某种 Future 。
乍一看,compose() 似乎没有明显的好处。 两种方法都接收String类型的值,但要返回普通的结果,compose()需要一个额外的步骤。
但在我们完全放弃 compose() 方法之前,让我们看看另一个场景:
Future<Future<String>> f = Future.succeededFuture(Future.succeededFuture("a"));
在这里,我们将 Future 包装在另一个 Future 中。这个例子乍一看似乎有些牵强,但稍后我将向您展示这种嵌套在异步框架中非常常见。
当我们尝试在这个嵌套的 Future 上调用 map() 和 compose() 时会发生什么?
Future<String> composeResult = f.compose(a -> a);
Future<String> mapResult = f.map(a -> a.result());
虽然以前的 compose() 代码更复杂,但现在是 map() 代码需要执行一些额外的工作。
To unnest the Future using map(), we need to invoke the result() method. With composeResult, though, the value is readily accessible. That’s because compose flattens the Future for us.
要使用 map() 取消嵌套 Future ,我们需要调用 result() 方法。但是,使用 composeResult ,该值很容易访问。那是因为compose 为我们扁平化了 Future 。
💡提示: 出于同样的原因,在 Vert.x 中
flatMap()只是compose()的别名。
因此,无论何时使用 Futures,compose() 实际上都是比 map() 更好的处理方式。
另一种查看方式是比较我们将identity function传递给两种方法时的结果:
Future<String> composeResult = f.compose(a -> a);
Future<Future<String>> mapResult = f.map(a -> a);
可视化此处发生的情况可能很有用:

注意颜色。 仅根据结果类型判断,您可以假设 compose() 将返回嵌套的 Future,而 map 将返回它接收到的相同对象。 这是不正确的。 compose() 和 map() 都返回一个新的 Future。
但是 compose 将实际值包装成一个Future,而 map 将底层的Future包装成一个新值。
我之前承诺过,我将演示在真实的 Vert.x 代码中可以在何处进行Future嵌套。 让我们看一下官方文档中的以下稍微简化的示例:
var future = client
.request(HttpMethod.GET, "some-uri")
.map(request -> request.send()
.map(response -> response
.body()
.map(buffer -> buffer.toJsonObject())));
此代码使用标准 Vert.x HttpClient 发送请求,然后读取响应正文并将其解析为 JSON。我相信你已经写了数百次类似的代码。
问题是,这里的结果是什么类型?
让我们明确类型:
Future<Future<Future<JsonObject>>> future = client
.request(HttpMethod.GET, "some-uri")
.map(request -> request.send()
.map(response -> response
.body()
.map(buffer -> buffer.toJsonObject())));
因为我们使用了 map() ,所以没有取消嵌套。 因此,为了读取 JSON,我们需要手动取消嵌套:
var json = future.result().result().result();
现在让我们用 compose() 替换 map() ,保持结果类型明确:
Future<JsonObject> future = client
.request(HttpMethod.GET, "some-uri")
.compose(request -> request.send()
.compose(response -> response
.body()
.map(buffer -> buffer.toJsonObject())));
结果类型现在很浅,并且易于使用:
var json = future.result();
请注意,最后一个方法仍然是 map() 。这是因为 body() 方法返回的是 Buffer 对象,而不是 Future ,因此无需平展它。
总结: 当 lambda 的参数为 Future 时,请使用 compose() 。当它是一个简单的对象时,请使用 map() 。
原文链接: https://alexey-soshin.medium.com/understanding-vert-x-compose-vs-map-d464dee78980



















