Introduction
In Python asyncio, calling an async function without await creates a coroutine object but never schedules it for execution. Python detects this and issues a RuntimeWarning: coroutine was never awaited. The intended async operation silently never runs, which is a common source of bugs in async codebases.
Symptoms
RuntimeWarning: coroutine 'fetch_data' was never awaitedprinted at program exit- Async operations appear to do nothing (no errors, no results)
- Warning includes the location where the unawaited coroutine was created:
`- sys:1: RuntimeWarning: coroutine 'send_notification' was never awaited
- RuntimeWarning: Enable tracemalloc to get the object allocation traceback
`- No exception is raised - the program continues normally
Common Causes
- Forgetting
awaitwhen calling an async function - Calling async function in a synchronous context without
asyncio.create_task() - Using
asyncio.ensure_future()incorrectly - Fire-and-forget pattern without proper task management
- Mixing sync and async code paths
Step-by-Step Fix
- 1.Identify the unawaited coroutine:
- 2.```bash
- 3.# Enable tracemalloc for detailed traceback
- 4.python -W error::RuntimeWarning your_script.py
- 5.# Or enable at runtime:
- 6.python -c "
- 7.import warnings
- 8.warnings.filterwarnings('error', category=RuntimeWarning)
- 9.import asyncio
- 10.asyncio.run(main())
- 11."
- 12.
` - 13.Fix missing await:
- 14.```python
- 15.# WRONG - coroutine created but never executed
- 16.async def main():
- 17.result = fetch_data(url) # Missing await!
- 18.print(result) # Prints <coroutine object>, not data
# CORRECT - await the coroutine async def main(): result = await fetch_data(url) print(result) # Prints actual data ```
- 1.Run async function from sync context properly:
- 2.```python
- 3.import asyncio
# WRONG - just creates coroutine object def sync_function(): fetch_data(url) # Does nothing
# CORRECT - run the async function def sync_function(): asyncio.run(fetch_data(url))
# Or in existing event loop context def sync_function(): loop = asyncio.get_event_loop() loop.run_until_complete(fetch_data(url)) ```
- 1.Fire-and-forget pattern with task management:
- 2.```python
- 3.import asyncio
background_tasks = set()
async def fire_and_forget(coro): """Schedule coroutine without awaiting it.""" task = asyncio.create_task(coro) background_tasks.add(task) task.add_done_callback(background_tasks.discard) return task
async def main(): # Send notification without blocking await fire_and_forget(send_notification(user_id, message)) # Continue with other work immediately
# Prevents "coroutine never awaited" warning # Task is tracked and cleaned up automatically ```
- 1.Use pytest-asyncio to catch unawaited coroutines in tests:
- 2.```python
- 3.import pytest
@pytest.mark.asyncio async def test_fetch_data(): # Test will fail if coroutine is not properly awaited result = await fetch_data("http://example.com") assert result is not None ```
Prevention
- Enable
PYTHONASYNCIODEBUG=1during development for stricter checks - Use
rufforflake8-asynclinters to detect missing awaits - Adopt the pattern: all async functions should be called with
awaitorcreate_task() - Use
asyncio.gather()for running multiple coroutines concurrently - Never call async functions from synchronous code without
asyncio.run()or event loop integration - Add
-W error::RuntimeWarningto your test runner to fail on unawaited coroutines