RuntimeError: await wasnt used with future in Async Comprehensions
Use explicit async for loops inside an async def function, or use await asyncio.gather(*[coro for coro in items]) to execute concurrent tasks cleanly.
Root Cause Analysis
This error occurs when Python's asynchronous runtime encounters a coroutine or future inside an asynchronous dictionary, list, or generator comprehension, but the internal event loop future was evaluated without an explicit await expression.
Evolution of Asynchronous Comprehensions in Python
Python 3.6 introduced asynchronous comprehensions (PEP 530), allowing [await f() for f in funcs] or {k: await v() for k, v in pairs} inside async def functions. However, mixing synchronous generator expressions with un-awaited coroutine objects creates dangling coroutine objects (<coroutine object ... at 0x...>)
When an asynchronous runner attempts to iterate over an unawaited future without driving the coroutine frame to completion, CPython raises RuntimeError: await wasn't used with future.
Common Triggers
- Missing
awaitinside Comprehensions: Writing{item: fetch_data(item) for item in items}instead of{item: await fetch_data(item) for item in items}. - Async Comprehensions at Top-Level Module Scope: Attempting async comprehensions outside an
async deffunction in older Python versions. - Mixing Synchronous
map()with Async Functions: Passing an async function tomap(async_fn, items)which returns unawaited coroutine generators.
Reproduction Code (MCVE)
# Simulates missing await with future runtime error
class FutureHolder:
pass
raise RuntimeError("await wasn't used with future - async dict comprehension in python3.8")
Solution 1: Use Concurrent Execution with `asyncio.gather()`
Create coroutines in a list comprehension and run them concurrently using asyncio.gather().
import asyncio
async def fetch_score(user_id: int) -> int:
await asyncio.sleep(0.01)
return user_id * 10
async def main():
user_ids = [1, 2, 3, 4, 5]
# Concurrent pattern: gather all coroutines in parallel
scores = await asyncio.gather(*(fetch_score(uid) for uid in user_ids))
results = dict(zip(user_ids, scores))
print(f'Computed results concurrently: {results}')
asyncio.run(main())
Solution 2: Use Async Comprehension Inside `async def`
Ensure the comprehension includes explicit await expressions and resides inside an async def function.
import asyncio
async def get_metadata(key: str) -> str:
await asyncio.sleep(0.01)
return f'val_{key}'
async def build_metadata_dict():
keys = ['alpha', 'beta', 'gamma']
# Valid async dict comprehension with explicit await
metadata_map = {k: await get_metadata(k) for k in keys}
print(f'Generated async dictionary: {metadata_map}')
asyncio.run(build_metadata_dict())
Common Pitfalls & Performance Comparison
Notice the performance difference: {k: await fetch(k) for k in items} executes sequentially (one after another), taking N * latency time. await asyncio.gather(*(fetch(k) for k in items)) executes concurrently in parallel, taking 1 * latency time. For network requests, prefer asyncio.gather() or asyncio.TaskGroup (Python 3.11+).
Contrasting with related async errors:
RuntimeError: await wasn't used with future: Future evaluated without await.RuntimeError: Event loop is closed: Accessing loop after shutdown.TypeError: object coroutine can't be used in 'await' expression: Calling await on non-awaitable.