Skillversitas

Coroutines and await

A coroutine is a function defined with async def. Calling it does not execute the body; it returns a coroutine object that can be paused and resumed. The await expression is what actually drives that: it suspends the current coroutine until the awaited object completes, and yields control back to the event loop in the meantime. Understanding this pair is the whole skill of asyncio.

Anatomy of a Coroutine

import asyncio

async def fetch(url):
    print("requesting", url)
    await asyncio.sleep(1)            # suspend: loop can run other tasks
    print("got", url)
    return f"data from {url}"

coro = fetch("https://api.example.com")   # body NOT run yet
print(type(coro))                          # <class 'coroutine'>

asyncio.run(coro)     # now it runs, to completion

What Can Be Awaited

Anything awaitable can follow await:

  • Coroutines (from async def).
  • Task objects - a coroutine scheduled on the loop (next chapter).
  • Future objects - a low-level "will eventually have a result" placeholder.
  • Objects implementing __await__ (for example, some third-party wrappers).

Chaining awaits is how async code composes: an async function can await other async functions, building a call tree where every leaf is an I/O operation that suspends without blocking the thread.

asyncio.sleep

asyncio.sleep(n) suspends the coroutine for n seconds and returns control to the loop; time.sleep(n) would instead freeze the entire loop for n seconds. The same distinction applies to all I/O: the stdlib offers async loop.sock_* methods for raw sockets, and third-party libraries (aiohttp, asyncpg, aiomysql) provide async versions of the usual clients.

async def tick(n):
    for i in range(3):
        print(f"tick {n}-{i}")
        await asyncio.sleep(0.2)

asyncio.run(asyncio.gather(tick(1), tick(2)))
# output interleaves: 1-0, 2-0, 1-1, 2-1, ...

Returning Values and Raising

A coroutine returns a value through return, which the awaiting expression receives, and it can raise exceptions, which propagate to whoever awaited it (or to the task's future). Cancellation is the third way a coroutine ends: asyncio.CancelledError is raised at the nearest await, and should be handled in finally or async with blocks rather than swallowed blindly.

Summary

Define work as async def, suspend with await at every I/O boundary, and let values and exceptions flow through the await chain. Replace every blocking call with its async counterpart, and the loop stays free to switch between your thousands of tasks.