Async & TestingLesson 1 of 2
Async/Await
Python's async/await syntax will look familiar: async def defines a coroutine function, and await waits for another coroutine. The model underneath is similar too: a single-threaded event loop switches between tasks whenever one of them is waiting.
The first difference: there is no event loop running by default. A Node script is already inside an event loop, but a Python script is not. You start one explicitly with asyncio.run(main()), usually once, at the entry point of your program. Top-level await works in JS modules, but not in Python scripts (only in the 'python -m asyncio' REPL and notebooks).
The second difference is subtle and important: a JS Promise starts running as soon as it is created, while calling a coroutine function in Python only creates a coroutine object. Nothing runs until you await it or schedule it with asyncio.create_task(). Forgetting the await gives you a "coroutine was never awaited" warning instead of a result.
For concurrency, asyncio.gather() is the Promise.all() equivalent, and gather(..., return_exceptions=True) behaves like Promise.allSettled(): failures come back as exception objects in the results list instead of crashing everything. Since Python 3.11, asyncio.TaskGroup offers structured concurrency: tasks created in an 'async with asyncio.TaskGroup()' block are all awaited when the block ends, and if one fails, the others are cancelled.
There is no built-in fetch(). The popular async HTTP clients are third-party: httpx (async with httpx.AsyncClient() as client: await client.get(url)) and aiohttp. Also avoid blocking calls like time.sleep() or requests.get() inside async code: they freeze the whole event loop. Use await asyncio.sleep() and async libraries, or push blocking work to a thread with asyncio.to_thread().
Quick reference
| JavaScript | Python | Note |
|---|---|---|
| async function fn() | async def fn(): | def, not function |
| await fetch(url) | await client.get(url) | httpx.AsyncClient (third-party) |
| Promise.all([...]) | await asyncio.gather(...) | Run concurrently |
| Promise.allSettled([...]) | gather(..., return_exceptions=True) | Errors returned, not raised |
| await new Promise(r => setTimeout(r, 1000)) | await asyncio.sleep(1) | Seconds, not ms |
| Promise starts immediately | asyncio.create_task(coro()) | Coroutines are lazy until awaited/scheduled |
| Top-level await (ES modules) | asyncio.run(main()) | Explicit entry point |
Side by side
// JavaScript
async function fetchUser(id) {
const res = await fetch(`/api/users/${id}`);
return await res.json();
}
const [user, posts] = await Promise.all([
fetchUser(1), fetchPosts(1)
]);
const results = await Promise.allSettled(ids.map(fetchUser));
const sleep = ms => new Promise(r => setTimeout(r, ms));
await sleep(1000);# Python
import asyncio
async def fetch_user(user_id):
await asyncio.sleep(0.1) # simulate network I/O
return {"id": user_id, "name": f"User_{user_id}"}
async def main():
# Promise.all
user, other = await asyncio.gather(fetch_user(1), fetch_user(2))
# Promise.allSettled
results = await asyncio.gather(
*[fetch_user(i) for i in range(10)],
return_exceptions=True,
)
good = [r for r in results if not isinstance(r, Exception)]
# Structured concurrency (3.11+)
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(fetch_user(1))
t2 = tg.create_task(fetch_user(2))
print(t1.result(), t2.result())
# Real HTTP (third-party): pip install httpx
# async with httpx.AsyncClient() as client:
# res = await client.get("https://api.example.com")
asyncio.run(main()) # entry pointExercise
Real Python, running in your browserWrite async Python code:
['data from /a', 'data from /b', 'data from /c']
Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment