In this lesson, you’ll learn how to convert a normal synchronous function into an asynchronous coroutine.
from random import randint
import time
import asyncio
async def randn():
await asyncio.sleep(3)
return randint(1, 10)
In this lesson, you’ll learn how to convert a normal synchronous function into an asynchronous coroutine.
from random import randint
import time
import asyncio
async def randn():
await asyncio.sleep(3)
return randint(1, 10)
00:00
Okay, let’s go ahead and turn this synchronous version of randn()
to an asynchronous version, and in the process turn it into a coroutine. The first thing we can do is change time.sleep()
to asyncio.sleep()
.
00:20 This is going to turn this from the old version, which was synchronous, and now this is asynchronous. Now, you’re probably wondering what is that going to do? Well, it’s going to make it a lot faster. And how’s it going to do it?
00:30 I’ll show that in just a minute. Let’s go ahead and go to IPython and see if this will run.
00:40
So, here’s IPython, and if I do randn()
—remember, last time it waited 3 seconds and then it spit out a value. Now it will instantly give me an error message saying the 'sleep'
thing was never awaited
.
00:52
Now, you’ll notice here that it says it’s a coroutine
, so this function went from a normal function, now Python is telling us, “Oh, this thing is actually a coroutine now.” And it says 'sleep' was never awaited
. What does this error message mean?
01:06
Well, anytime you do something that’s asynchronous, you need to wait for it, and in Python we call that await
. Now, you might think, “Does this thing mean the same as before?” Like, when you did time.sleep()
, it’s going to wait 3 seconds, it’s going to sleep, and wait 3 seconds. This time, when you do await
, it actually doesn’t wait. Well, let me actually rewind that a little bit.
01:32 What this means is any code that happens after this function will not happen until this happens. So line 12 has to happen first in order for line 13 to happen.
01:41
But I could be down here in main()
and be doing other things, and those other things would still be running. So this await
allows this particular function to pause, but other things in Python that are still quote-unquote “running” can still run, okay? While this is running, you could imagine it running in the background until it’s finished.
02:05 In that case, the system says, “Hey, I’m done sleeping,” and then it notifies us that the sleeping has finished, and then after it notifies us then line 13 executes. Okay.
02:17
So, once again, I got this error message down here that says 'sleep'
was not awaited. Okay, so I did an await
on that. What happens if I save this, and I flip back and try it again? I’m going to exit out
02:34
and then run—oh, okay. We automatically got an error message. It says await
happened outside of an asynchronous function. That’s another point, is anytime you do an await
inside of a function like this, it cannot be a normal function—it has to be an asynchronous function.
02:49
And how you change a function from normal into asynchronous is you say async
.
02:57
So, this function is asynchronous, and inside of asynchronous functions, you have to use the await
keyword and you have to do an await
on something that happens, like, in the background, for example like writing to a disk, writing to a database, calling a website—something that happens outside of Python’s process.
03:18
Like, things considered IO, or input/output. Okay, so we’re going to wait 3
seconds and now let’s try it. So I’m going to save this, come back here.
03:33
No error message, that’s good. And if I do randn()
…
03:38
I got no error messages, that’s great. And now it says now I have not a generator, like I got before, but now it says you have a <coroutine object>
, right? The other one was odds()
, let’s just show that. odds(3, 11)
.
03:53
So that was a generator, which is something that produces values. This randn()
is a coroutine—you could think of it as consuming values. It’s more of a consumer, whereas a generator is more like a producer. Okay.
04:06 But both of these things, generators and coroutines, allow you to pause execution at any time. Okay, so let’s exit out of this. Now, you may think “I have this coroutine, but how do I actually run it?” Well, how you run a coroutine is a little bit strange and it involves something called an event loop.
04:27
This is how the event loop works. What I’m going to do is, I need to call randn()
inside of main()
, so I’m going to say—because randn()
is asynchronous itself, I can’t just say, like, random value is equal to randn()
.
04:50
You may think that, “Oh, I’m going to call randn()
, here it is, and its return value, this random number, will go into r
.” Right?
04:57
You may think that, and then let’s go back here and just see what happens if I try to do that—python theory.py
.
05:06
And it says coroutine 'randn' was never awaited
. So once you have something that’s asynchronous, you always have to use await
. Okay, so because asyncio.sleep()
was asynchronous, you had to do await
. Because randn()
is asynchronous, you also have to await
it as well, too.
05:27
Okay? So, let’s try that. I come back here and run this again, and it says, “Now you’re doing an await
outside of an asynchronous function.” So now, this main()
cannot be a regular function—it also has to turn into an asynchronous function.
UBBA on May 16, 2019
No event loop in this lesson. Next lesson.
Pygator on Sept. 14, 2019
Don’t understand how a coroutine is a consumer? It produced a random number here. I see how generators are produces, because they “generate” sequences one value at a time.
fofanovis on April 9, 2020
Don’t understand how a coroutine is a consumer? It produced a random number here. I see how generators are produces, because they “generate” sequences one value at a time.
If you watch more, you’ll see that they consume output from async funcs using await. In that sense, they are consumers.
al on May 25, 2025
This is 100% unrelated with the content itself. It’s a grumpy developer’s personal rant:
I can’t be the only one that finds it “dumb” to have to use both ‘async’ to define a coroutine and ‘await’ to call that coroutine.
Clearly python itself knows what functions were defined with ‘async’ (otherwise it wouldn’t be able to raise an Exception when I try to call that async def without the - disgusting - ‘await’ keyword.). Also, it is not a matter of choice/control: it is never ever allowed to call an ‘async’ function without ‘await’. So why? Why do I need to make the code a lot more verbose by adding ‘await this’, ‘await that’, ‘await that_other_thing’ all over the place?
Developers should only be required to be explicit with the ‘async’ keyword while defining the function and then that function should be called as all functions are called. The interpreter surely could handle that.
Bartosz Zaczyński RP Team on May 26, 2025
@al First of all, the async
/ await
pattern is pretty common in many modern languages, not just Python. While it might seem redundant at first, there are situations where working directly with the returned awaitable object (like a coroutine or a future) is actually useful, especially if you’re building frameworks or need to manage tasks more flexibly. So, even though it may look a bit verbose, the explicitness gives you more control and makes your code’s async flow clearer.
Become a Member to join the conversation.
Vaibhav Chauhan on April 21, 2019
so what’s the event loop in this example ?