How to Run Python Async Functions with asyncio.run() and await
The Problem
I wrote my first async function in Python. It looked simple:
async def fetch_data(): await asyncio.sleep(1) return "data fetched"Then I tried to run it:
result = fetch_data()print(result)I got this:
<coroutine object fetch_data at 0x7f8b2c1d3e80>Not the result I expected. The function didn’t execute. It just returned a coroutine object.
This is the most common confusion for Python developers learning async. You define async def, but the function doesn’t run when you call it directly.
Why This Happens
Async functions in Python are coroutines. When you call an async function directly (without await or asyncio.run()), you get a coroutine object, not the result.
Synchronous Code | v asyncio.run() <-- Entry point (creates event loop) | v async function | v await <-- Suspends execution, returns control to event loop | v ResultThe async function needs an event loop to run. Calling it directly doesn’t create one.
The Solution
Use asyncio.run() as your entry point from synchronous code:
import asyncio
async def fetch_data(): await asyncio.sleep(1) # simulates IO operation return "data fetched"
# Entry point - run from synchronous coderesult = asyncio.run(fetch_data())print(result) # "data fetched"asyncio.run() does three things:
- Creates a new event loop
- Runs your coroutine until it completes
- Closes the event loop
This is the recommended way to run async code from synchronous Python since Python 3.7.
Nested Async Calls
When calling async functions inside another async function, use await:
import asyncio
async def fetch_data(): await asyncio.sleep(1) return "data fetched"
async def process(): data = await fetch_data() # Must use await return data.upper()
# Entry pointasyncio.run(process())Without await, the inner coroutine is created but never executed:
async def process_wrong(): data = fetch_data() # WRONG: creates coroutine, doesn't run it # data is a coroutine object, not "data fetched" return data.upper() # This will failUsing the asyncio REPL
Python 3.8+ includes an asyncio REPL for experimentation:
python -m asyncioInside the REPL, you can use await directly:
>>> import asyncio>>> await asyncio.sleep(1, result='hello')'hello'>>> async def test():... return 42>>> await test()42This is useful for learning and debugging async code without needing to wrap everything in asyncio.run().
Common Mistakes
Mistake #1: Calling async function directly
async def my_func(): return "hello"
result = my_func() # WRONG# result is coroutine object, not "hello"Fix:
result = asyncio.run(my_func()) # CORRECTMistake #2: Using asyncio.run() inside an async context
async def outer(): async def inner(): return "hello" return asyncio.run(inner()) # WRONG: RuntimeErrorYou can’t nest asyncio.run() calls. Each call creates a new event loop, but you’re already inside one.
Fix:
async def outer(): async def inner(): return "hello" return await inner() # CORRECT: use await insteadMistake #3: Blocking the event loop
async def bad_example(): time.sleep(10) # WRONG: blocks the entire event loop return "done"time.sleep() blocks the event loop. All other async tasks stop running while this sleeps.
Fix:
async def good_example(): await asyncio.sleep(10) # CORRECT: non-blocking sleep return "done"When to Use Async
asyncio excels at IO-bound operations:
- HTTP requests (aiohttp, httpx)- Database queries (asyncpg, databases)- File operations (aiofiles)- Network sockets- Multiple concurrent tasks
Poor Use Cases:- CPU-intensive calculations- Local synchronous file operations- Simple one-off operationsFor IO-bound work, async lets you do multiple operations concurrently without blocking:
import asyncioimport aiohttp
async def fetch_url(session, url): response = await session.get(url) return await response.text()
async def fetch_all(urls): async with aiohttp.ClientSession() as session: tasks = [fetch_url(session, url) for url in urls] return await asyncio.gather(*tasks) # Run all concurrently
asyncio.run(fetch_all(['http://example.com', 'http://example.org']))All URLs are fetched concurrently, not sequentially. This is where async shines.
Summary
To run Python async functions:
- Define with
async def - Call from sync code using
asyncio.run(my_func()) - Call from async code using
await my_func() - Don’t block the event loop with synchronous operations
The pattern is simple: one entry point (asyncio.run()) and await everywhere else.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments