Sunday 6 February 2022

async/await JavaScript - Python differences

I've been working a bit with async/await in Python lately, and there are some slight differences with JavaScript that I will document here.

Python's asynchronous machinery is provided by the asyncio module. We have coroutines, Tasks and Futures, I already talked about it, and I won't copy/paste the documentation, I'll just say that most of the time you will just work with coroutines (coroutine functions are those declared with async and they return a coroutine object. We can think of coroutine objects as JavaScript Promises.

JavaScript runtimes start an event loop on their own (nothing runs in JavaScript without an event loop), but in Python you have to start it yourself by calling asyncio.run(). Python's event-loop and coroutines are based on generators, and it does not use a ThreadPool like .Net does. This is well explained here and here

OK, so let's go now to the practical stuff, those minor differences with JavaScript

The most important difference is that coroutines are Lazy. Invoking a coroutine function returns a coroutine object, but the code that we wrote inside the function won't be executed until an await is done on that coroutine. In Javascript the code inside an async function starts to run as soon as we invoke the function.


import asyncio
import time

async def test():
    print("inside test")
    aux = await asyncio.sleep(1)
    return "test result"

async def main():
    print('inside main')
    aux = asyncio.sleep(1)
    print(type(aux)) #<class 'coroutine'>
    await aux
    print('next')
    aux = test()
    print(type(aux)) #<class 'coroutine'>
    #if we don't do an await aux, "test" is never executed
    #and we get a warning:
    #RuntimeWarning: coroutine 'test' was never awaited


#very interesting, the code in the "main" coroutine is not started until we call asyncio.run
#(I assume inside asyncio.run there is an await)
#it's different from javascript
cr = main()
print(type(cr))
time.sleep(3)
asyncio.run(cr)

In Javascript we can await for a non promise value. I mean, we can do:
let a = await "yyy";.
What happens here is that the JavaScript engine does a call to Promise.resolve(x) for each "await x;" sentence that it finds. If "x" is a Promise, it just returns that Promise, else, it returns a Promise that resolves to x.
This is not like that in Python. Awaiting for a non awaitable (coroutine, Task or Future) value throws an exception:


a = await "a"
#TypeError: object str can't be used in 'await' expression

In JavaScript when we await for a Promise that resolves to another Promise, the await will await for that internal promise, and so on...


(async () => {
	let pr = new Promise(resFn => setTimeout(() => {
		console.log("resolving first Promise");
		resFn(new Promise(resFn2 => setTimeout(() => {
				console.log("resolving second Promise");
				resFn2("Bonjour!");
		}, 3000)));
	}, 2000));
	let txt = await pr;
	console.log(txt);
	//after 5 seconds Bonjour gets printed
})();

This is not like that in Python. If a coroutine resolves to another coroutine, it's that second coroutine what await will "return"


import asyncio

async def __getCity__():
    await asyncio.sleep(1)
    return "Paris"


async def getCity():
    await asyncio.sleep(0.5)
    print("after first sleep")
    #if the coroutine resolves to another coroutine, the "await" in "main" calling "getCity" does not wait for the internal one (contrary to JavaScript)
    return __getCity__()
    
    #so we should write this line rather than the one above
    #return await __getCity__()


async def main():
    
    city = await getCity()
    print(city)

asyncio.run(main())

#after first sleep
#<coroutine object __getCity__ at 0x7fad74b34540>


We can say that for the 2 previous cases JavaScript works differently because of what seems to be known as the sloppy promise semantics

No comments:

Post a Comment