Future results
Vert.x 4 use futures to represent asynchronous results.
Any asynchronous method returns a 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.
FileSystem fs = vertx.fileSystem();
Future<FileProps> future = fs.props("/my_file.txt");
future.onComplete((AsyncResult<FileProps> ar) -> {
if (ar.succeeded()) {
FileProps props = ar.result();
System.out.println("File size = " + props.size());
} else {
System.out.println("Failure: " + ar.cause().getMessage());
}
});
|
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 props 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
|
|
Caution
|
Terminal operations like Consider a future on which 2 callbacks are registered:
It is possible that the second callback is invoked before the first one. If you need such guarantee, consider using Future composition with |
Future composition
compose 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
FileSystem fs = vertx.fileSystem();
Future<Void> future = fs
.createFile("/foo")
.compose(v -> {
// When the file is created (fut1), execute this:
return fs.writeFile("/foo", Buffer.buffer());
})
.compose(v -> {
// When the file is written (fut2), execute this:
return fs.move("/foo", "/bar");
});
In this example, 3 operations are chained together:
-
a file is created
-
data is written in this file
-
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, Future offers more: map, recover, otherwise, andThen and even a flatMap which is an alias of compose
Future coordination
Coordination of multiple futures can be achieved with Vert.x futures.
It supports concurrent composition (run several async operations in parallel) and sequential composition (chain async operations).
Future.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:
Future<HttpServer> httpServerFuture = httpServer.listen();
Future<NetServer> netServerFuture = netServer.listen();
Future.all(httpServerFuture, netServerFuture).onComplete(ar -> {
if (ar.succeeded()) {
// All servers started
} else {
// At least one server failed
}
});
The operations run concurrently, the 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.
On success, the resultAt method guarantees the results in the same order specified in the call to Future.all. In the example above, regardless of which item completed first, the httpServer result can be accessed using resultAt(0) and the netServer result can be accessed using resultAt(1).
Alternatively, you can pass a list (potentially empty) of futures:
Future.all(Arrays.asList(future1, future2, future3));
While the all composition waits until all futures are successful (or one fails), the any composition waits for the first succeeded future. Future.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:
Future.any(future1, future2).onComplete(ar -> {
if (ar.succeeded()) {
// At least one is succeeded
} else {
// All failed
}
});
A list of futures can be used also:
Future.any(Arrays.asList(f1, f2, f3));
The join composition waits until all futures are completed, either with a success or a failure. Future.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:
Future.join(future1, future2, future3).onComplete(ar -> {
if (ar.succeeded()) {
// All succeeded
} else {
// All completed and at least one failed
}
});
A list of futures can be used also:
Future.join(Arrays.asList(future1, future2, future3));
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 toCompletionStage method, as in:
Future<String> future = vertx.createDnsClient().lookup("vertx.io");
future.toCompletionStage().whenComplete((ip, err) -> {
if (err != null) {
System.err.println("Could not resolve vertx.io");
err.printStackTrace();
} else {
System.out.println("vertx.io => " + ip);
}
});
We can conversely go from a CompletionStage to Vert.x Future using Future.fromCompletionStage.
There are 2 variants:
-
the first variant takes just a
CompletionStageand calls theFuturemethods from the thread that resolves theCompletionStageinstance, and -
the second variant takes an extra
Contextparameter to call theFuturemethods 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:
Future.fromCompletionStage(completionStage, vertx.getOrCreateContext())
.flatMap(str -> {
String key = UUID.randomUUID().toString();
return storeInDb(key, str);
})
.onSuccess(str -> {
System.out.println("We have a result: " + str);
})
.onFailure(err -> {
System.err.println("We have a problem");
err.printStackTrace();
});