Skip to content

Commit

Permalink
Future terminal operation documentation (#4447)
Browse files Browse the repository at this point in the history
* Javadoc: document behavior of Future terminal operations

Signed-off-by: Thomas Segismont <[email protected]>

* Add andThen method to Future API

This allows to guarantee order of execution for callbacks at the expense of creating new future instances.

Signed-off-by: Thomas Segismont <[email protected]>

* Extract futures documentation from index.adoc

Signed-off-by: Thomas Segismont <[email protected]>

* Futures documentation: add caution section about terminal operations

Signed-off-by: Thomas Segismont <[email protected]>

* Updates after review

Signed-off-by: Thomas Segismont <[email protected]>

* Fix doc (compilation failure)

Signed-off-by: Thomas Segismont <[email protected]>
  • Loading branch information
tsegismont authored Aug 8, 2022
1 parent 37c6310 commit c57696a
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 155 deletions.
157 changes: 157 additions & 0 deletions src/main/asciidoc/futures.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
== Future results

Vert.x 4 use futures to represent asynchronous results.

Any asynchronous method returns a {@link io.vertx.core.Future} object for the result of the call:
a _success_ or a _failure_.

You cannot interact directly with the result of a future, instead you need to set a handler that will be called when the future completes and the result is available, like any other kind of event.

[source,$lang]
----
{@link examples.CoreExamples#exampleFuture1}
----

NOTE: Vert.x 3 provides a callback-only model.
To allow an easy migration to Vert.x 4 we decided that each asynchronous method has also a callback version.
The `props` method above has also a version {@link io.vertx.core.file.FileSystem#props(java.lang.String, io.vertx.core.Handler)} with a callback as method argument.

[CAUTION]
====
Do not confuse _futures_ with _promises_.
If futures represent the "read-side" of an asynchronous result, promises are the "write-side".
They allow you to defer the action of providing a result.
In most cases, you don't need to create promises yourself in a Vert.x application.
<<_future_composition>> and <<_future_coordination>> provide you with the tools to transform and merge asynchronous results.
However, if, in your codebase, you have legacy methods which use callbacks, you can leverage the fact that a promise extends {@link io.vertx.core.Handler io.vertx.core.Handler<io.vertx.core.AsyncResult>}:
[source,$lang]
----
{@link examples.CoreExamples#promiseAsHandler}
----
====

[CAUTION]
====
Terminal operations like `onSuccess`, `onFailure` and `onComplete` provide no guarantee whatsoever regarding the invocation order of callbacks.
Consider a future on which 2 callbacks are registered:
[source,$lang]
----
{@link examples.CoreExamples#promiseCallbackOrder}
----
It is possible that the second callback is invoked before the first one.
If you need such guarantee, consider using <<_future_composition>> with {@link io.vertx.core.Future#andThen}.
====

[#_future_composition]
== Future composition
{@link io.vertx.core.Future#compose(java.util.function.Function)} can be used for chaining futures:
- when the current future succeeds, apply the given function, that returns a future.
When this returned future completes, the composition succeeds.
- when the current future fails, the composition fails
[source,$lang]
----
{@link examples.CoreExamples#exampleFutureComposition1}
----
In this example, 3 operations are chained together:
1. a file is created
2. data is written in this file
3. the file is moved
When these 3 steps are successful, the final future (`future`) will succeed.
However, if one of the steps fails, the final future will fail.
Beyond this, {@link io.vertx.core.Future} offers more: `map`, `recover`, `otherwise`, `andThen` and even a `flatMap` which is an alias of `compose`
[#_future_coordination]
== Future coordination
Coordination of multiple futures can be achieved with Vert.x {@link io.vertx.core.Future futures}.
It supports concurrent composition (run several async operations in parallel) and sequential composition (chain async operations).
{@link io.vertx.core.CompositeFuture#all} takes several futures arguments (up to 6) and returns a future that is
_succeeded_ when all the futures are _succeeded_ and _failed_ when at least one of the futures is failed:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAll1}
----

The operations run concurrently, the {@link io.vertx.core.Handler} attached to the returned future is invoked upon completion of the composition.
When one of the operation fails (one of the passed future is marked as a failure), the resulting future is marked as failed too.
When all the operations succeed, the resulting future is completed with a success.

Alternatively, you can pass a list (potentially empty) of futures:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAll2}
----

While the `all` composition _waits_ until all futures are successful (or one fails), the `any` composition
_waits_ for the first succeeded future. {@link io.vertx.core.CompositeFuture#any} takes several futures arguments (up to 6) and returns a future that is succeeded when one of the futures is, and failed when all the futures are failed:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAny1}
----

A list of futures can be used also:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAny2}
----

The `join` composition _waits_ until all futures are completed, either with a success or a failure.
{@link io.vertx.core.CompositeFuture#join} takes several futures arguments (up to 6) and returns a future that is succeeded when all the futures are succeeded, and failed when all the futures are completed and at least one of them is failed:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureJoin1}
----

A list of futures can be used also:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureJoin2}
----

=== CompletionStage interoperability

The Vert.x `Future` API offers compatibility _from_ and _to_ `CompletionStage` which is the JDK interface for composable asynchronous operations.

We can go from a Vert.x `Future` to a `CompletionStage` using the {@link io.vertx.core.Future#toCompletionStage} method, as in:

[source,$lang]
----
{@link examples.CompletionStageInteropExamples#toCS}
----

We can conversely go from a `CompletionStage` to Vert.x `Future` using {@link io.vertx.core.Future#fromCompletionStage}.
There are 2 variants:

. the first variant takes just a `CompletionStage` and calls the `Future` methods from the thread that resolves the `CompletionStage` instance, and
. the second variant takes an extra {@link io.vertx.core.Context} parameter to call the `Future` methods on a Vert.x context.

IMPORTANT: In most cases the variant with a `CompletionStage` and a `Context` is the one you will want to use to respect the Vert.x threading model, since Vert.x `Future` are more likely to be used with Vert.x code, libraries and clients.

Here is an example of going from a `CompletionStage` to a Vert.x `Future` and dispatching on a context:

[source,$lang]
----
{@link examples.CompletionStageInteropExamples#fromCS}
----
150 changes: 1 addition & 149 deletions src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -199,155 +199,7 @@ Vert.x will also provide stack traces to pinpoint exactly where the blocking is
If you want to turn off these warnings or change the settings, you can do that in the
{@link io.vertx.core.VertxOptions} object before creating the Vertx object.

== Future results

Vert.x 4 use futures to represent asynchronous results.

Any asynchronous method returns a {@link io.vertx.core.Future} object for the result of the call:
a _success_ or a _failure_.

You cannot interact directly with the result of a future, instead you need to set a handler that will be called when the future
completes and the result is available, like any other kind of event.

[source,$lang]
----
{@link examples.CoreExamples#exampleFuture1}
----

NOTE: Vert.x 3 provides a callback-only model.
To allow an easy migration to Vert.x 4 we decided that each asynchronous method has also a callback version.
The `props` method above has also a version {@link io.vertx.core.file.FileSystem#props(java.lang.String, io.vertx.core.Handler)} with a callback as method argument.

[CAUTION]
====
Do not confuse _futures_ with _promises_.
If futures represent the "read-side" of an asynchronous result, promises are the "write-side".
They allow you to defer the action of providing a result.
In most cases, you don't need to create promises yourself in a Vert.x application.
<<_future_composition>> and <<_future_coordination>> provide you with the tools to transform and merge asynchronous results.
However, if, in your codebase, you have legacy methods which use callbacks, you can leverage the fact that a promise extends {@link io.vertx.core.Handler io.vertx.core.Handler<io.vertx.core.AsyncResult>}:
[source,$lang]
----
{@link examples.CoreExamples#promiseAsHandler}
----
====

[#_future_composition]
== Future composition

{@link io.vertx.core.Future#compose(java.util.function.Function)} can be used for chaining futures:

- when the current future succeeds, apply the given function, that returns a future. When this returned future completes, the composition succeeds.
- when the current future fails, the composition fails

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureComposition1}
----

In this example, 3 operations are chained together:

1. a file is created
2. data is written in this file
3. the file is moved

When these 3 steps are successful, the final future (`future`) will succeed. However, if one
of the steps fails, the final future will fail.

Beyond this, {@link io.vertx.core.Future} offers more: `map`, `recover`, `otherwise` and even a `flatMap` which is an alias of `compose`

[#_future_coordination]
== Future coordination

Coordination of multiple futures can be achieved with Vert.x {@link io.vertx.core.Future futures}. It
supports concurrent composition (run several async operations in parallel) and sequential composition
(chain async operations).

{@link io.vertx.core.CompositeFuture#all} takes several futures arguments (up to 6) and returns a future that is
_succeeded_ when all the futures are _succeeded_ and _failed_ when at least one of the futures is failed:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAll1}
----

The operations run concurrently, the {@link io.vertx.core.Handler} attached to the returned future is invoked upon
completion of the composition. When one of the operation fails (one of the passed future is marked as a failure),
the resulting future is marked as failed too. When all the operations succeed, the resulting future is completed
with a success.

Alternatively, you can pass a list (potentially empty) of futures:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAll2}
----

While the `all` composition _waits_ until all futures are successful (or one fails), the `any` composition
_waits_ for the first succeeded future. {@link io.vertx.core.CompositeFuture#any} takes several futures
arguments (up to 6) and returns a future that is succeeded when one of the futures is, and failed when
all the futures are failed:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAny1}
----

A list of futures can be used also:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureAny2}
----

The `join` composition _waits_ until all futures are completed, either with a success or a failure.
{@link io.vertx.core.CompositeFuture#join} takes several futures arguments (up to 6) and returns a future that is
succeeded when all the futures are succeeded, and failed when all the futures are completed and at least one of
them is failed:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureJoin1}
----

A list of futures can be used also:

[source,$lang]
----
{@link examples.CoreExamples#exampleFutureJoin2}
----

=== CompletionStage interoperability

The Vert.x `Future` API offers compatibility _from_ and _to_ `CompletionStage` which is the JDK interface for composable
asynchronous operations.

We can go from a Vert.x `Future` to a `CompletionStage` using the {@link io.vertx.core.Future#toCompletionStage} method, as in:

[source,$lang]
----
{@link examples.CompletionStageInteropExamples#toCS}
----

We can conversely go from a `CompletionStage` to Vert.x `Future` using {@link io.vertx.core.Future#fromCompletionStage}.
There are 2 variants:

. the first variant takes just a `CompletionStage` and calls the `Future` methods from the thread that resolves the `CompletionStage` instance, and
. the second variant takes an extra {@link io.vertx.core.Context} parameter to call the `Future` methods on a Vert.x context.

IMPORTANT: In most cases the variant with a `CompletionStage` and a `Context` is the one you will want to use to respect the Vert.x threading model,
since Vert.x `Future` are more likely to be used with Vert.x code, libraries and clients.

Here is an example of going from a `CompletionStage` to a Vert.x `Future` and dispatching on a context:

[source,$lang]
----
{@link examples.CompletionStageInteropExamples#fromCS}
----
include::futures.adoc[]

== Verticles

Expand Down
9 changes: 9 additions & 0 deletions src/main/java/examples/CoreExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ public void promiseAsHandler() {
Future<String> greeting = promise.future();
}

public void promiseCallbackOrder(Future<Void> future) {
future.onComplete(ar -> {
// Do something
});
future.onComplete(ar -> {
// May be invoked first
});
}

private void legacyGreetAsync(Handler<AsyncResult<String>> promise) {
}

Expand Down
30 changes: 27 additions & 3 deletions src/main/java/io/vertx/core/Future.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ static <T> Future<T> failedFuture(String failureMessage) {

/**
* Add a handler to be notified of the result.
* <br/>
* <p>
* <em><strong>WARNING</strong></em>: this is a terminal operation.
* If several {@code handler}s are registered, there is no guarantee that they will be invoked in order of registration.
*
* @param handler the handler that will be called with the result
* @return a reference to this, so it can be used fluently
*/
Expand All @@ -113,7 +116,10 @@ static <T> Future<T> failedFuture(String failureMessage) {

/**
* Add a handler to be notified of the succeeded result.
* <br/>
* <p>
* <em><strong>WARNING</strong></em>: this is a terminal operation.
* If several {@code handler}s are registered, there is no guarantee that they will be invoked in order of registration.
*
* @param handler the handler that will be called with the succeeded result
* @return a reference to this, so it can be used fluently
*/
Expand All @@ -128,7 +134,10 @@ default Future<T> onSuccess(Handler<T> handler) {

/**
* Add a handler to be notified of the failed result.
* <br/>
* <p>
* <em><strong>WARNING</strong></em>: this is a terminal operation.
* If several {@code handler}s are registered, there is no guarantee that they will be invoked in order of registration.
*
* @param handler the handler that will be called with the failed result
* @return a reference to this, so it can be used fluently
*/
Expand Down Expand Up @@ -345,6 +354,21 @@ default Future<T> otherwiseEmpty() {
return (Future<T>) AsyncResult.super.otherwiseEmpty();
}

/**
* Invokes the given {@code handler} upon completion.
* <p>
* If the {@code handler} throws an exception, the returned future will be failed with this exception.
*
* @param handler invoked upon completion of this future
* @return a future completed after the {@code handler} has been invoked
*/
default Future<T> andThen(Handler<AsyncResult<T>> handler) {
return transform(ar -> {
handler.handle(ar);
return (Future<T>) ar;
});
}

/**
* Bridges this Vert.x future to a {@link CompletionStage} instance.
* <p>
Expand Down
Loading

0 comments on commit c57696a

Please sign in to comment.