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

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.

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:

future.onComplete(ar -> {
  // Do something
});
future.onComplete(ar -> {
  // May be invoked first
});

It is possible that the second callback is invoked before the first one.

If you need such guarantee, consider using Future composition with andThen.

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:

  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, 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:

  1. the first variant takes just a CompletionStage and calls the Future methods from the thread that resolves the CompletionStage instance, and

  2. the second variant takes an extra Context parameter to call the Future methods on a Vert.x context.

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