Tasks and gather
Awaiting coroutines one after another runs them one after another. To overlap their waiting, run them as tasks.
import asyncio
async def fetch(name, delay):
await asyncio.sleep(delay)
return name
async def main():
results = await asyncio.gather(
fetch("slow", 0.01),
fetch("fast", 0.0),
)
print(results) # ['slow', 'fast']
asyncio.run(main())gather wraps each coroutine in a task, runs them all at once, and returns their results in the order you passed them, not the order they finished. asyncio.create_task(coro) starts a single task in the background; await it later for its result.
When one fails
By default, the first exception from any task propagates out of gather, and you lose the other results. With return_exceptions=True, exceptions come back as values in the list, in their place, and every task runs to the end. Test each item with isinstance(item, Exception).
Your task
Write async def fetch_all(keys, fetch) that runs fetch(key) for every key at the same time and returns a dict from each key to its result. Leave out the keys whose fetch raised an exception.
The explanation opens here once every check passes.