What does it mean that Vert.x is "based on callbacks" (instead of futures)?

Viewed 200

When talking about futures and callbacks, the documentation says that

The Vert.x core APIs are based on callbacks to notify of asynchronous events. The seasoned developer will naturally think that this opens the door to the so-called "callback hell" where multiple levels of nested callbacks render the code difficult to comprehend as illustrated by this fictional code:

foo.a(1, res1 -> {
  if (res1.succeeded()) {
    bar.b("abc", 1, res2 -> {
      if (res.succeeded()) {
         baz.c(res3 -> {
           dosomething(res1, res2, res3, res4 -> {
               // (...)
           });
         });
      }
    });
  }
});

While the core APIs could have been designed to favor promises and futures, the choice of callbacks as the cardinal model is actually interesting since it allows different programming abstractions to be used.

  1. What does it mean that "The Vert.x core APIs are based on callbacks"? What would be different if it was based on futures?
  2. What does "callbacks as the cardinal model" mean?
1 Answers
  1. It means that the Vert.x core asynchronous APIs all take callbacks as input, and use that callback to handle reacting to the asynchronous result of the API call. If it was based on Futures, instead of an API like this:

      void deployVerticle(Verticle verticle, Handler<AsyncResult<String>> 
          completionHandler);
    

    It would look like this:

    Future<String> deployVerticle(Verticle verticle);
    

    So instead of taking a callback that gets an AsyncResult<String> as input, it returns a Future<String>. However, the Vert.x team is moving to a hybrid Callback/Future-based model in Vert.x 4.0, so both of these kinds of APIs will be part of the core Vert.x API in the near-future.

  2. I believe cardinal is used here to mean "fundamental". So it just means that Vert.x itself uses callbacks in its own implementation. It then uses code generation and/or other techniques to derive other kinds of API abstractions. Like APIs that return Futures, or return RxJava types, or can be used as Kotlin coroutines, etc.

In general, Futures are easier to work with than callbacks, especially when you have to compose several asynchronous operations together. For example, the code you included in your original question could probably be written like this, using Futures:

CompositeFuture.all(foo.a(1), bar.b("abc", 1), baz.c())
    .compose(fut -> doSomething(fut.resultAt(0), fut.resultAt(1),
        fut.resultAt(2)))
    .compose(res4 -> ...)
Related