A3.B Resultados, errores y esperas¶
Lanzar una tarea es solo la mitad: casi siempre querrás recoger su resultado y saber si falló. Lo que representa «un resultado que todavía no existe» recibe distintos nombres según el lenguaje, pero la idea es la misma.
Resultados futuros en Java¶
CompletableFuture<T> representa un resultado futuro. supplyAsync(funcion) lo calcula en otro hilo; join() (o get()) espera y devuelve el valor. Si la tarea falla, join() lanza una CompletionException (y get() una ExecutionException) que envuelve la excepción real: se obtiene con getCause(). Para limitar la espera: get(tiempo, unidad), que lanza TimeoutException, o orTimeout(...).
| Qué | En Java |
|---|---|
| Un valor que llegará | CompletableFuture<T> / Future<T> |
| Esperar el valor | f.join() / f.get() |
| Lanzar varias y esperar todas | CompletableFuture.allOf |
| Límite de tiempo | get(t, unidad) |
Un ejemplo: tres consultas y un error¶
Se consultan los precios de tres productos a la vez (cada consulta tarda 100 ms), se suman, y después se pide un producto que no existe para ver cómo llega el error:
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
// Tareas que DEVUELVEN un resultado (o un error): se lanzan todas a la vez y se recogen con join().
public class Resultados {
static final Map<String, Integer> PRECIOS = Map.of("pan", 2, "leche", 1, "queso", 5);
static int precio(String producto) {
try {
Thread.sleep(100); // simula una consulta lenta
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Integer p = PRECIOS.get(producto);
if (p == null) throw new IllegalArgumentException("producto desconocido: " + producto);
return p;
}
public static void main(String[] args) {
List<String> productos = List.of("pan", "leche", "queso");
// supplyAsync ejecuta la función en otro hilo y devuelve un CompletableFuture con el resultado futuro
List<CompletableFuture<Integer>> consultas = productos.stream()
.map(p -> CompletableFuture.supplyAsync(() -> precio(p))) // las tres avanzan a la vez
.toList();
int total = 0;
for (int i = 0; i < productos.size(); i++) {
int p = consultas.get(i).join(); // espera ese resultado
System.out.println("precio de " + productos.get(i) + ": " + p);
total += p;
}
System.out.println("total: " + total);
// un error dentro de la tarea llega envuelto en una CompletionException; la causa es la excepción real
try {
CompletableFuture.supplyAsync(() -> precio("caviar")).join();
} catch (CompletionException e) {
System.out.println("error: " + e.getCause().getMessage());
}
}
}
Salida:
precio de pan: 2
precio de leche: 1
precio de queso: 5
total: 8
error: producto desconocido: caviar
Cuidado con la envoltura: se captura CompletionException y el mensaje original está en e.getCause().getMessage().
Los resultados se muestran en el orden en que se pidieron, aunque las consultas hayan terminado en otro. Es lo que se espera de una lista de resultados.
Esperar no es lo mismo que bloquear
await, join o get esperan al resultado. En los modelos con await (Dart, Kotlin, Python) esa espera deja libre al hilo para otras tareas. En join()/get() de Java el hilo que espera se queda parado: es lo normal en un programa de consola, pero no en una interfaz gráfica, que se congelaría.
Límites de tiempo y cancelación¶
Una tarea que tarda demasiado puede dejar tu programa colgado. Todo lenguaje ofrece una espera con límite (mira la tabla de arriba): si se pasa el tiempo, obtienes un error o un valor vacío y decides qué hacer, por ejemplo reintentar o avisar. Cancelar la tarea libera recursos, pero en muchos casos solo se solicita y la tarea tiene que cooperar.
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Lanzar varias tareas y esperarlas una por una al lanzarlas (cada una espera a la anterior) | Lánzalas todas primero y luego espera: así avanzan a la vez |
| Olvidar capturar el error de una tarea | El error llega al esperar el resultado: ahí va el try |
| Esperar sin límite una operación de red | Pon un límite de tiempo |
| Reintentar sin pausa ni tope | Limita el número de intentos (ejercicio A3.4) y espera entre ellos |
Para practicar¶
Los ejercicios A3.3 y A3.4 usan un límite de tiempo y reintentos. Para ver la misma idea en los otros lenguajes: Pasar de uno a otro.