BuildBot

Python by Contrast

Async by contrast

Lesson 4 of 5

What you'll learn

  • Start an event loop explicitly with asyncio.run
  • Map async def, await and gather onto async, await and Promise.all
  • Understand why a blocking sync call freezes the whole loop

In JS the event loop is the runtime — every script already lives inside it. Python programs are synchronous by default; the asyncio event loop exists only once you start it, and the idiomatic entry point is a single asyncio.run(main()) at the bottom of the file.

import asyncio

async def fetch(name: str, delay: float) -> str:
    await asyncio.sleep(delay)   # non-blocking: yields to the loop
    return f"{name} done"

async def main() -> None:
    results = await asyncio.gather(
        fetch("a", 0.3), fetch("b", 0.2), fetch("c", 0.1)
    )
    print(results)  # ['a done', 'b done', 'c done'] — order preserved

asyncio.run(main())  # starts the loop, runs main, closes the loop

The TS shape is nearly identical, minus the explicit startup — and asyncio.gather is Promise.all, down to preserving input order:

async function fetchOne(name: string, ms: number): Promise<string> {
  await new Promise((r) => setTimeout(r, ms));
  return `${name} done`;
}
const results = await Promise.all([fetchOne("a", 300), fetchOne("b", 200)]);

Coroutines are lazy

Calling a JS async function starts it immediately — promises are eager. Calling fetch("a", 0.3) in Python starts nothing: it returns a coroutine object that only runs once awaited or scheduled (asyncio.create_task is the "start now" move, like calling an async function in JS and awaiting later).

One blocking call freezes everything

The loop is cooperative and single-threaded, like JS. A synchronous call inside a coroutine — time.sleep(1), a requests.get — doesn't just slow that task, it blocks the entire loop: nothing else runs until it returns. Use asyncio.sleep, an async client like httpx, or push sync work off the loop with asyncio.to_thread.

await is not concurrency

Awaiting coroutines one at a time serializes them, exactly like sequential awaits in JS. gather is the concurrency move for I/O; CPU-bound work still wants threads or processes, not the event loop.

The challenge is a JS model of gather: the same delayed tasks run one at a time, then together — watch the timings.

gather vs sequential awaits (JS model)

Run it. This models asyncio.gather with Promise.all: sequential awaits cost the sum of the delays, gather costs only the longest one.

Loading editor…
Knowledge check

What does calling an async def function (without awaiting it) do in Python?

Next: the standard library that makes Python the default language of ops scripts and glue code.

Saved on this device. Sign in to sync your progress everywhere.