Skip to main content

gather, Timeouts, and Failure Modes

intermediate14 min readLesson 99 of 169

Run many coroutines with return_exceptions=True and never wait forever.

asyncio.gather runs a collection of coroutines concurrently and collects results โ€” with one knob that changes its failure philosophy:

import asyncio

async def job(name, delay, fail=False):
    await asyncio.sleep(delay)
    if fail:
        raise ValueError(name)
    return name

async def main():
    results = await asyncio.gather(
        job("a", 0.1),
        job("b", 0.1, fail=True),
        job("c", 0.1),
        return_exceptions=True,       # failures become VALUES, not crashes
    )
    # ["a", ValueError("b"), "c"]

With return_exceptions=False (default), the first exception cancels the gang and re-raises โ€” right for all-or-nothing work. With True, each failure is returned in place โ€” right for collect what you can, like fetching from many flaky sources. Choosing between them is the design decision.

Timeouts: nobody waits forever

async def main():
    try:
        result = await asyncio.wait_for(job("slow", 10), timeout=0.5)
    except asyncio.TimeoutError:
        result = "gave up"

wait_for cancels the coroutine when the deadline passes and raises TimeoutError in the caller. Real collectors combine the two: gather with return_exceptions=True, wrap each job in a timeout, and treat (timeout, error) results as "failed sources" โ€” the exact shape of this module's checkpoint.

Now practice

gather and Timeout DrillsCollect what you can; never wait forever.2 challenges ยท ยท ~30 min