Vert.x Future and Promise

Vert.x Future and Promise

In this tutorial, we are going to discuss about the Vert.x Future and Promise. In Vert.x, Futures and Promises are used to manage asynchronous operations, making it easier to write and handle non-blocking code. Vert.x provides a reactive, event-driven model where operations like network calls, database interactions, and file IO are non-blocking, which is where Futures and Promises come into play.

We know that Vert.x uses an asynchronous programming model to achieve scalability and resource efficiency. We discussed about event loops where only asynchronous tasks should be executed in an asynchronous environment. It is possible to use callbacks to manage tasks and react on different results. There is one problem with callbacks. Chaining callbacks together can get very messy.

You may know the term of callback hell. callback hell is a situation in programming where multiple callbacks are nested inside one another, making the code difficult to understand and maintain. Callback hell is a common occurrence in asynchronous programming, such as when handling events, making HTTP requests, or reading files. This is because each asynchronous operation requires a callback function, and when one operation depends on the result of another, the code structure becomes deeply nested. Chaining callbacks together can get very messy. Good that there is a better way. In Vert.x we have promises and futures.

Vert.x Future and Promise

Vert.x Future and Promise allow you to represent the eventual result (or failure) of an asynchronous operation and define what should happen when the operation completes.

Promise is used to write an eventual value and can be completed, or it can be marked as failed. A future is used to read the value from the promise when it is available. You can think of a read and a write view on a value. The promise should live in a small context. For example, inside a function. A future, on the other hand, can be returned and chained together to act on the result of the promise

A promise can be created by writing Promise.promise() and the result of the promise can be of different types.

Promise<String> promise = Promise.promise();

// Async task simulation
vertx.setTimer(1000, handler -> {
    // Complete the promise (which will complete the future)
    promise.complete("Success!");
});

In the above example, the result will be of type string. To simulate asynchronous code, a vertex timer is used and the promise is completed after a 1000 milliseconds delay with the message success. A promise can be completed in multiple ways and contain different values.

Promise<Void> returnVoid = Promise.promise();
returnVoid.complete();
Promise<String> returnString = Promise.promise();
returnVoid.complete("Hello");
Promise<String> returnJsonObject = Promise.promise();
returnJsonObject.complete(new JsonObject().put("message","hello"));

The first one is a promise Void, which doesn’t contain any value. The second one is a promise with a String and it completes with the string Hello. And the third one is a promise with the return type JsonObject. It completes with a new JsonObject output message hello. So we can also use the vertex Json objects and vertex json arrays.

Here are a few examples illustrating how to use Vert.x Future and Promise for asynchronous programming in a Vert.x environment:

1. Basic Future Example

In this example, we create a Future and use it to handle a simple asynchronous operation.

package com.ashok.vertx.vertx_starter.future;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
/**
 *
 * @author ashok.mariyala
 *
 */
public class FutureExample {

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();

    Future<String> future = Future.future(promise -> {
      // Simulate an async operation (e.g., fetching data)
      vertx.setTimer(1000, timerId -> {
        promise.complete("Task completed successfully!");
      });
    });

    // Handle the result of the future
    future.onComplete(result -> {
      if (result.succeeded()) {
        System.out.println("Result: " + result.result());
      } else {
        System.out.println("Failure: " + result.cause());
      }
    });
  }
}

Output

Result: Task completed successfully!

2. Basic Promise Example

A Promise is used to produce the result for the Future. Here’s how you can manually complete or fail a promise.

package com.ashok.vertx.vertx_starter.promise;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;

/**
 *
 * @author ashok.mariyala
 *
 */
public class PromiseExample {

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();

    // Create a Promise
    Promise<String> promise = Promise.promise();

    // Simulate an async task
    vertx.setTimer(1000, timerId -> {
      if (timerId > 0) {
        promise.complete("Success from promise!");
      } else {
        promise.fail(new RuntimeException("Something went wrong"));
      }
    });

    // Get the future from the promise
    promise.future().onComplete(result -> {
      if (result.succeeded()) {
        System.out.println("Result: " + result.result());
      } else {
        System.out.println("Failure: " + result.cause());
      }
    });
  }
}

Output

Failure: java.lang.RuntimeException: Something went wrong

3. Composing Futures

This example demonstrates how to chain multiple futures using compose to perform sequential asynchronous tasks.

package com.ashok.vertx.vertx_starter.future;

import io.vertx.core.Future;
import io.vertx.core.Vertx;

/**
 *
 * @author ashok.mariyala
 *
 */
public class ComposeFutureExample {

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();

    // First async task
    Future<String> future1 = Future.future(promise -> {
      vertx.setTimer(1000, timerId -> {
        promise.complete("First task completed");
      });
    });

    // Composing with second async task
    Future<String> future2 = future1.compose(result1 -> {
      return Future.future(promise -> {
        vertx.setTimer(1000, timerId -> {
          promise.complete(result1 + " -> Second task completed");
        });
      });
    });

    // Handle the final result
    future2.onComplete(result -> {
      if (result.succeeded()) {
        System.out.println("Final result: " + result.result());
      } else {
        System.out.println("Failure: " + result.cause());
      }
    });
  }
}

Output

Final result: First task completed -> Second task completed

4. Combining Multiple Futures with Future.all()

Sometimes you may want to execute multiple asynchronous operations concurrently and wait for all of them to complete. This is where Future.all() comes in handy.

package com.ashok.vertx.vertx_starter.future;

import io.vertx.core.Future;
import io.vertx.core.Vertx;

import java.util.Arrays;

/**
 *
 * @author ashok.mariyala
 *
 */
public class FutureAllExample {

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();

    // Create three independent async tasks
    Future<String> future1 = Future.future(promise -> vertx.setTimer(1000, timerId -> promise.complete("Task 1 completed")));
    Future<String> future2 = Future.future(promise -> vertx.setTimer(2000, timerId -> promise.complete("Task 2 completed")));
    Future<String> future3 = Future.future(promise -> vertx.setTimer(1500, timerId -> promise.complete("Task 3 completed")));

    // Combine all futures
    Future.all(Arrays.asList(future1, future2, future3))
      .onComplete(result -> {
        if (result.succeeded()) {
          System.out.println("Result : "+result.result());
          System.out.println("All tasks completed successfully");
        } else {
          System.out.println("Some task failed: " + result.cause());
        }
      });
  }
}

Output

Result : Future{result=(Future{result=Task 1 completed},Future{result=Task 2 completed},Future{result=Task 3 completed})}
All tasks completed successfully

5. Handling Failures with Future.recover()

In this example, we handle an asynchronous failure and recover from it using recover().

package com.ashok.vertx.vertx_starter.future;

import io.vertx.core.Future;
import io.vertx.core.Vertx;

/**
 *
 * @author ashok.mariyala
 *
 */
public class FutureRecoverExample {

  public static void main(String[] args) {
    Vertx vertx = Vertx.vertx();

    // Simulate a task that fails
    Future<String> future = Future.future(promise -> {
      vertx.setTimer(1000, timerId -> {
        promise.fail(new RuntimeException("Task failed"));
      });
    });

    // Recover from failure
    future
      .recover(throwable -> {
        System.out.println("Recovering from failure: " + throwable.getMessage());
        // Return a new future to continue with an alternative result
        return Future.succeededFuture("Recovered task result");
      })
      .onComplete(result -> {
        if (result.succeeded()) {
          System.out.println("Result: " + result.result());
        } else {
          System.out.println("Failure: " + result.cause());
        }
      });
  }
}

These examples demonstrate how Vert.x handles asynchronous operations using Vert.x Future and Promise for tasks that might succeed, fail, or need to be chained together.

Best Practices
  • Use Promises to create and complete Futures.
  • Use Futures to handle the results of asynchronous operations.
  • Chain Futures together to create complex asynchronous workflows.
  • Handle errors using Promises and Futures.
  • Consider using Vert.x’s io.vertx.core.eventbus.EventBus for more complex asynchronous communication patterns.

That’s all about the Vert.x Future and Promise with examples. By understanding and effectively utilizing Vert.x Future and Promises, you can build more robust, responsive, and scalable applications. If you have any queries or feedback, please write us email at contact@waytoeasylearn.com. Enjoy learning, Enjoy Vert.x tutorials..!!

Vert.x Future and Promise
Scroll to top