CodeGym /Cursos /JAVA 25 SELF /Combinación de CompletableFuture: thenCombine, allOf, any...

Combinación de CompletableFuture: thenCombine, allOf, anyOf

JAVA 25 SELF
Nivel 55, Lección 2
Disponible

1. Combinamos CompletableFuture

En la vida real rara vez todo llega de una sola fuente: el perfil del usuario y sus pedidos se pueden cargar en paralelo, los datos de dos microservicios — combinar, y a veces solo queremos procesar la primera respuesta que llegue. El enfoque síncrono aquí obliga a esperar en serie; la clase CompletableFuture permite lanzar todo a la vez y combinar los resultados de forma elegante. Para ello ofrece métodos específicos: thenCombine, allOf, anyOf. Veamos cada uno por orden.

Tareas paralelas: dos peticiones asíncronas

Cómo se vería de forma síncrona:

String name = loadUserName();    // lento
int balance = loadUserBalance(); // lento
System.out.println("Nombre: " + name + ", Saldo: " + balance);

Problema: la segunda llamada solo comenzará tras finalizar la primera.

Forma asíncrona

Con CompletableFuture ambas tareas se pueden lanzar simultáneamente:

CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> loadUserName());
CompletableFuture<Integer> balanceFuture = CompletableFuture.supplyAsync(() -> loadUserBalance());

Pero ¿cómo obtener ahora ambos resultados y procesarlos juntos? Para eso sirve thenCombine.

2. thenCombine: combinamos los resultados de dos tareas

El método thenCombine permite combinar dos CompletableFuture y ejecutar una acción cuando ambas tareas finalicen. Devuelve un nuevo CompletableFuture con el resultado de la combinación.

Firma:

<A, B, C> CompletableFuture<C> thenCombine(
    CompletionStage<? extends B> other,
    BiFunction<? super A, ? super B, ? extends C> fn
)
  • A — tipo del resultado del primer future,
  • B — tipo del segundo,
  • C — tipo del resultado combinado.

Ejemplo

CompletableFuture<String> nameFuture = CompletableFuture.supplyAsync(() -> loadUserName());
CompletableFuture<Integer> balanceFuture = CompletableFuture.supplyAsync(() -> loadUserBalance());

CompletableFuture<String> resultFuture = nameFuture.thenCombine(
    balanceFuture,
    (name, balance) -> "Nombre: " + name + ", Saldo: " + balance
);

resultFuture.thenAccept(System.out::println);

Cómo funciona:

  • Ambos futures se inician en paralelo.
  • En cuanto ambos terminan, se invoca la función (name, balance) -> ....
  • El future resultante contiene la cadena con el resultado.

Mini ejemplo con números

CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(() -> 3);
CompletableFuture<Integer> sum = f1.thenCombine(f2, Integer::sum);

sum.thenAccept(result -> System.out.println("Suma: " + result));

Salida:

Suma: 5

Variante asíncrona

Si la combinación es una operación pesada, usa thenCombineAsync:

f1.thenCombineAsync(f2, (a, b) -> a * b);

3. allOf: cuando hay muchas tareas

¿Y si no tenemos dos tareas, sino toda una decena? Por ejemplo, queremos cargar en paralelo los datos de diez usuarios a la vez. Para ello está el método CompletableFuture.allOf.

Descripción

CompletableFuture.allOf(f1, f2, ..., fn) devuelve un nuevo future que finalizará cuando finalicen todas las tareas pasadas. Pero hay un matiz: este future no contiene resultado — su tipo es siempre CompletableFuture<Void>. Para obtener los resultados, hay que «extraerlos» de los futures originales.

Ejemplo

CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Primero");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "Segundo");

CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2);

all.thenRun(() -> {
    // ¡Todas las tareas han finalizado!
    String s1 = f1.join(); // join() — como get(), pero lanza una excepción no comprobada
    String s2 = f2.join();
    System.out.println(s1 + " & " + s2);
});

Salida:

Primero & Segundo

Ejemplo con un array de tareas

List<CompletableFuture<String>> futures = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    int id = i;
    futures.add(CompletableFuture.supplyAsync(() -> "Usuario " + id));
}

CompletableFuture<Void> all = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

all.thenRun(() -> {
    for (CompletableFuture<String> f : futures) {
        System.out.println(f.join());
    }
});

Qué ocurre:

  • Todas las tareas se inician en paralelo.
  • allOf finalizará cuando finalicen todas las tareas.
  • En el bloque thenRun podemos obtener los resultados mediante join().

Esquema visual

[Future1]   \
[Future2] ----> [allOf] ---> thenRun
[Future3]   /

4. anyOf: esperamos la primera tarea que termine

A veces no hay que esperar a todas, sino obtener el resultado de la tarea más rápida. Por ejemplo, solicitamos datos a dos servidores — usamos los datos del que responda primero. Para esto está CompletableFuture.anyOf.

Descripción

CompletableFuture.anyOf(f1, f2, ..., fn) devuelve un future que se completará cuando se complete cualquiera de las tareas pasadas. El tipo de resultado es CompletableFuture<Object>, porque los tipos de las tareas pueden ser distintos.

Ejemplo

CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> {
    sleep(500);
    return "Servidor rápido";
});
CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
    sleep(2000);
    return "Servidor lento";
});

CompletableFuture<Object> any = CompletableFuture.anyOf(fast, slow);

any.thenAccept(result -> System.out.println("Recibido: " + result));

Salida:

Recibido: Servidor rápido

Ejemplo con tipos distintos
Se pueden combinar tareas de distintos tipos, pero entonces el resultado será de tipo Object, y se requerirá un casting explícito.

5. Aspectos útiles

Recomendaciones

  • allOf no devuelve un array de resultados. Debes conservar los futures originales para obtener sus valores mediante join() o get().
  • anyOf devuelve el primer resultado que termine, pero de tipo Object. Si todas las tareas son del mismo tipo, puedes hacer un casting.
  • Si alguna de las tareas en allOf termina con error — el future resultante también fallará.
  • Para thenCombine ambas tareas deben completarse correctamente; de lo contrario, habrá una excepción.

Tabla comparativa de los métodos

Método Cuándo usar Tipo de resultado
thenCombine
Necesitas combinar los resultados de dos tareas Resultado de la combinación
allOf
Necesitas esperar a que finalicen todas las tareas
Void
anyOf
Necesitas esperar a que finalice cualquiera de las tareas Object (resultado de la primera tarea)

6. Errores típicos al combinar CompletableFuture

Error n.º 1: Esperar el resultado mediante get()/join() en el hilo principal.
Si escribes código asíncrono pero al final llamas igualmente a get() o join(), estás bloqueando el hilo y pierdes todas las ventajas de la asincronía. Es mejor usar thenAccept/thenRun para procesar el resultado sin bloqueo.

Error n.º 2: No guardar referencias a los futures originales al usar allOf.
Si llamaste a CompletableFuture.allOf(f1, f2, f3), pero no guardaste f1, f2, f3, no podrás obtener sus resultados. ¡allOf devuelve solo Void!

Error n.º 3: No manejar los errores en la cadena.
Si una de las tareas termina con error, todo allOf o thenCombine también fallará. Usa los métodos de manejo de errores (exceptionally, handle, whenComplete) para no dejar pasar las excepciones.

Error n.º 4: Desajuste de tipos con anyOf.
anyOf devuelve Object. Si tus futures devuelven tipos distintos, tendrás que averiguar qué llegó primero. Es mejor usar tipos de tarea homogéneos cuando sea posible.

Error n.º 5: Cadenas demasiado complejas sin comentarios.
Cuando el código crece, las cadenas de futures pueden convertirse en «espagueti». No dudes en dividir la cadena en variables intermedias y comentar los pasos.

1
Tarea
JAVA 25 SELF, nivel 55, lección 2
Bloqueada
Coordinación de Entregas: Espera de Todas las Cargas
Coordinación de Entregas: Espera de Todas las Cargas
1
Tarea
JAVA 25 SELF, nivel 55, lección 2
Bloqueada
Construcción del saludo: Unir fragmentos del mensaje
Construcción del saludo: Unir fragmentos del mensaje
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION