Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

BTW I wonder why async is so painless in ES6 compared to Python. Why the presence of GIL (which JS also has) did not make running async coroutines completely transparent, as it made running generators (which are, well, coroutines already). Why the whole even loop thing is even visible at all.


Because JavaScript never had threads so I/O in JavaScript has always been non-blocking and the whole ecosystem surrounding it has grown up under that assumption.

JavaScript doesn't need a GIL because it doesn't really have threads. WebWorkers are more akin to multiprocessing than threads in Python. Objects cannot be shared directly across WebWorkers so transferring data comes with the expense of serializing/deserializing at the boundary.


JS now has shared array buffers.


SharedArrayBuffer is just raw memory similar to using mmap from Python multiprocessing. The developer experience is very different to simply sharing objects across threads.


> Why the whole even loop thing is even visible at all.

It isn't anymore.

    In [3]: from asyncio import run

    In [4]: async def async_func(): print('Ran async_func()')

    In [5]: run(async_func())
    Ran async_func()
Top-level async/await is also available in the Python REPL and IPython, and there are discussions on the Python mailing list about making top-level async/await the default for Python[1].

    In [1]: async def async_func(): print('Ran async_func()')

    In [2]: await async_func()
    Ran async_func()

[1] https://groups.google.com/g/python-ideas/c/PN1_j7Md4j0/m/0xy...


Oh, top level await... I missed that.

Not sure it will get there, but it would be nice. I think putting a top level "await" is explicit enough for stating you want an event loop anyway.

Now, with TaskGroup in 3.11, things are going to get pretty nice, espacially if this top level await plays out, provided they include async for and async with in the mix.

Now, if they could just make so that coroutines are automatically schedules to the nearest task group, we would almost have something usable.


I used them both extensively, and here are the main reasons I can think of:

- The event loop in JS is invisible and implicit. V8 proved it can be done without paying a cost for it, and in fact most real life python projects are using uvloop because it's faster than asyncio default loop. JS dev don't think of the loop at all, because it's always been there. They don't have to chose a loop, or thinking about its lifecycle or scheduling. The API doesn't show the loop at all.

- Asynchronous functions in JS are scheduled automatically. On python, calling a coroutine function does...nothing. You have to either await it, or pass it to something like asyncio.create_task(). The later is not only verbose, it's not intuitive.

- Async JS functions can be called from sync functions transparently. It just returns a Promise after all, and you can use good old callbacks. Instantiating a Python coroutine does... nothing as we said. You need to schedule it AND await it. If you don't, it may or may not be executed. Which is why asyncio.gather() and co are to be used in python. Most people don't know that, and even if you know, it's verbose, and you can forget. All that, again, because using the event loop must be explicit. That's one thing TaskGroup from trio will help with in the next Python versions...

- the early asyncio API sucked. The new one is ok, asyncio.run() and create_task() with implicit loop is a huge improvement. But you better use 3.7 at least. And you have to think about all the options for awaiting: https://stackoverflow.com/questions/42231161/asyncio-gather-...

- asyncio tutorials and docs are not great, people have no idea how to use it. Since it's more complex, it compounds.

E.G, if you use await:

With node v14.8+:

    await async_func(params)
With python 3.7+:

    import asyncio 

    async def main():
        # no top level await, it must happen in a loop
        await async_func(params) 

    asyncio.run(main) # explicit loop, but easy one thanks to 3.7
E.G, deep inside functions calls, but no await:

With node:

    ...
    async_func(params)

With python 3.7+:

    ...
    # async_func(params) alone would do nothing
    res = asyncio.create_task(async_func(params))

    ...

    # you MAY get away with not using gather() or wait()
    # but you also may get "coroutine is never awaited"
    # RuntimeWarning: coroutine 'async_func' was never awaited
    asyncio.gather(res)
Of course, you could use "run_until_complete()", but then you would be blocking. Which is just not possible in JS, there is one way to do it, and it's always non blocking and easy. Ironic, isn't it? Beside, which Python dev knows all this? I'm guessing most readers of this post will have heard of it for the first time.

Python is my favorite language, and I can live with the explicit loop, but explicit scheduling is ridiculous. Just run the damn coroutine, I'm not instantiating it for the beauty of it. If I want a lazy construct, I can always make a factory.

Now, thanks to the trio nursery concept, we will get TaskGroup in the next release (also you can already use them with anyio):

    async with asyncio.TaskGroup() as tg:
        tg.start_soon(async_func, params)
Which, while still verbose, is way better:

- no gather or wait. Schedule it, it will run or be cleaned up.

- no need to chose an awaiting strat, or learn about a 1000 things. This works for every cases. Wanna use it in a sync call ? Pass the tg reference in it.

- lifecycle is cleanly scoped, a real problem with a lot of async code (including in JS, where it doesn't have a clean solution)


> Python is my favorite language, and I can live with the explicit loop, but explicit scheduling is ridiculous. Just run the damn coroutine

You can't "just run the damn coroutine", that's not what coroutines are.

But that does point to the mistake of Python, at least from a UX perspective: coroutines are more efficient but they're also a lot more prone to use errors, especially in dynamically typed languages. Async functions should probably just have been tasks, which is what they are in JS.


I know, but we could make coroutine creates with async have a special marker that is different to yield and auto schedule.


That’s what a task is.


I’m not an expert on Node.js but I was always under the impression that you couldn’t write await outside of an async function. Has that changed recently?


You can await at the top level of a module since node v14.8.

It has been proposed to Python: https://groups.google.com/g/python-ideas/c/PN1_j7Md4j0/m/0xy...

But I have no more info about it.


You are right that you can only await in an async function, but you can call an async function anywhere and get back a Promise.

await-ing that promise is often what you want to do but there are other ways to do work once the promise has resolved.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: