Basic

await and return

Calling an async function does not run it. It makes a task, and only an await starts the body and takes the value back.

What you will see

1 2

About 2 min of reading

Try It

Check what a call and an await each do.

1

Coroutine object.

import asyncio async def slow(): return 4 print(type(slow()))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
2

Awaited value.

import asyncio async def slow(): return 4 async def main(): print(await slow()) asyncio.run(main())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
4
3

Task reused.

import asyncio async def slow(): return 4 async def main(): t = asyncio.ensure_future(slow()) a = await t b = await t print(a + b) asyncio.run(main())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
8
4

Fresh call runs.

import asyncio runs = 0 async def slow(): global runs runs += 1 return runs async def main(): print(await slow(), await slow()) asyncio.run(main())

Before you run it, say out loud what it will print. Then open the answer.

What it prints
1 2

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

The call makes a task, the await starts the body, the return value is handed back, and a fresh call makes a fresh task.
1 call the function
↓
2 keep the task
↓
3 await starts the body
↓
4 the value comes back
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

import asyncio sets nothing on screen
async def slow(): sets nothing on screen
return 4 sets nothing on screen
print(type(slow())) <class 'coroutine'>
Why this works
A coroutine is a recipe

Calling an async function writes down the work without doing it. The await is the verb, and forgetting one leaves a plan nobody runs.

One run, many reads

A task caches its result, so awaiting the same task twice is cheap. Awaiting two fresh calls of the same function runs it twice.

Examples

Calling without await

import asyncio

async def slow():
    return 4

print(slow())

The call returns a coroutine object. The body never runs, which is the most common silent bug here.

Awaiting it

import asyncio

async def slow():
    return 4

async def main():
    print(await slow())

asyncio.run(main())

await starts the body and hands back whatever it returns.

await needs async

async def slow():
    return 4

def outer():
    return await slow()

A plain function cannot await. That is a syntax error caught when the file loads, not at run time.

Passing a task around

import asyncio

async def slow():
    return 4

async def main():
    task = asyncio.ensure_future(slow())
    print(await task)

asyncio.run(main())

A task is a value. It can be stored, passed, and awaited somewhere else.

Awaiting the same task twice

import asyncio

async def slow():
    return 4

async def main():
    task = asyncio.ensure_future(slow())
    a = await task
    b = await task
    print(a + b)

asyncio.run(main())

The body runs once. The second await reads the finished result, so the work is not repeated.

A coroutine you forgot

async def slow():
    return 4

async def main():
    slow()

asyncio.run(main())

A bare call leaves a coroutine that is never closed. The warning names the line, which is how the mistake is found.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Waiting without blocking
Previous Lesson
Waiting without blocking
Next Lesson
Order is not finished order
Order is not finished order →