Saltar a contenido

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 Python

Una función async def devuelve una corrutina, y await espera su valor sin bloquear el bucle. asyncio.gather(*corrutinas) las ejecuta a la vez y devuelve la lista de resultados en el mismo orden. Si una falla, gather propaga la primera excepción. Para limitar la espera: asyncio.wait_for(corrutina, segundos), que lanza asyncio.TimeoutError.

Qué En Python
Un valor que llegará corrutina / Future
Esperar el valor await c
Lanzar varias y esperar todas asyncio.gather
Límite de tiempo asyncio.wait_for

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:

# Tareas que DEVUELVEN un resultado (o un error): se lanzan todas a la vez y se recogen con await.
import asyncio

PRECIOS = {"pan": 2, "leche": 1, "queso": 5}


async def precio(producto):
    await asyncio.sleep(0.1)  # simula una consulta lenta
    if producto not in PRECIOS:
        raise ValueError(f"producto desconocido: {producto}")
    return PRECIOS[producto]


async def main():
    productos = ["pan", "leche", "queso"]

    # gather lanza las tres a la vez y devuelve una lista con los resultados, en el mismo orden
    resultados = await asyncio.gather(*(precio(p) for p in productos))
    for producto, p in zip(productos, resultados):
        print(f"precio de {producto}: {p}")
    print(f"total: {sum(resultados)}")

    # un error dentro de una tarea se lanza al hacer await, y se captura con try/except como siempre
    try:
        await precio("caviar")
    except ValueError as e:
        print(f"error: {e}")


asyncio.run(main())

Salida:

precio de pan: 2
precio de leche: 1
precio de queso: 5
total: 8
error: producto desconocido: caviar

El error se lanza al hacer await (o en gather), como una excepción normal: se captura con try/except.

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.