Skip to lesson
Python for JS Developers

Async & TestingLesson 1 of 2

Async/Await

4 min

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

JavaScriptPythonNote
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 immediatelyasyncio.create_task(coro())Coroutines are lazy until awaited/scheduled
Top-level await (ES modules)asyncio.run(main())Explicit entry point

Side by side

JavaScript
// 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
# 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 point

Exercise

Real Python, running in your browser

Write async Python code:

A coroutine fetch(url) is already defined for you. It takes 0.2s and raises ValueError for URLs containing "bad". 1. async def fetch_all(urls): - fetch all URLs concurrently with asyncio.gather - use return_exceptions=True - return only the successful results (drop the exceptions) 2. async def main(): print(await fetch_all(["/a", "/b", "/bad", "/c"])) 3. Entry point: asyncio.run(main())
Expected output
['data from /a', 'data from /b', 'data from /c']

Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment