Live data from Hacker News

Asynchronous Everything (2015)

joeduffyblog.com

1–10 of 26 posts

Re: Asynchronous Everything (2015)

#2
Async will continue to look awkward until we drop explicit "await" on function calls. Awaiting should be the default. If I call "async foo()" then I get back a promise I can explicitly await, but there should be no semantic difference between calling a synchronous function or asynchronous without modifiers.

Not only it makes it more consistent, but you can implement running a synchronous function into a separate thread just by doing "async foo()". Can't get easier.

Lua already has the right idea here (check coroutines), and why it's so useful in game scripts.

Re: Asynchronous Everything (2015)

#4
post #2

Async will continue to look awkward until we drop explicit "await" on function calls. Awaiting should be the default. If I call "async foo()" then I get back a promise I can explicitly await, but there should be no semantic difference between calling a synchronous function or asynchronous without modifiers. Not only it makes it more consistent, but you can implement running a synchronous function into a separate thre…

Agreed. That's basically what Go does.

All functions are async, calling functions does an implicit await, and the go keyword let's you spawn a task without await. (At least that's what semantically happens) This way you can write seemingly imperative blocking code, and have it work fully asynchronously.

It's great, freeing, and avoids the function coloring problem. It's really the feature I miss most when writing other languages.

Though I do understand why performance oriented languages like Rust might want to approach it differently.

I don't, however, see a reason for non-performance oriented ones to use function coloring (unless they're doing full semicolon customization, like Haskell with its monad stacks).

Re: Asynchronous Everything (2015)

#5
post #4
post #2

Async will continue to look awkward until we drop explicit "await" on function calls. Awaiting should be the default. If I call "async foo()" then I get back a promise I can explicitly await, but there should be no semantic difference between calling a synchronous function or asynchronous without modifiers. Not only it makes it more consistent, but you can implement running a synchronous function into a separate thre…

Agreed. That's basically what Go does. All functions are async, calling functions does an implicit await, and the go keyword let's you spawn a task without await. (At least that's what semantically happens) This way you can write seemingly imperative blocking code, and have it work fully asynchronously. It's great, freeing, and avoids the function coloring problem. It's really the feature I miss most when writing oth…

Performance-oriented languages tend to have a strong type system which lets them know well ahead of time what function is being called, so I see no reason why what you describe can't be done there without performance overhead.

Re: Asynchronous Everything (2015)

#6
post #4
post #2

Async will continue to look awkward until we drop explicit "await" on function calls. Awaiting should be the default. If I call "async foo()" then I get back a promise I can explicitly await, but there should be no semantic difference between calling a synchronous function or asynchronous without modifiers. Not only it makes it more consistent, but you can implement running a synchronous function into a separate thre…

Agreed. That's basically what Go does. All functions are async, calling functions does an implicit await, and the go keyword let's you spawn a task without await. (At least that's what semantically happens) This way you can write seemingly imperative blocking code, and have it work fully asynchronously. It's great, freeing, and avoids the function coloring problem. It's really the feature I miss most when writing oth…

> Though I do understand why performance oriented languages like Rust might want to approach it differently.

The thing Rust gets wrong however, is that there should be a smooth path from ergonomics to performance.

Re: Asynchronous Everything (2015)

#7
post #2

Async will continue to look awkward until we drop explicit "await" on function calls. Awaiting should be the default. If I call "async foo()" then I get back a promise I can explicitly await, but there should be no semantic difference between calling a synchronous function or asynchronous without modifiers. Not only it makes it more consistent, but you can implement running a synchronous function into a separate thre…

I’d like to see a way to opt in to this behavior, for example with a “use await” within a function or module, similar to “use strict” but without making it a global setting.

Re: Asynchronous Everything (2015)

#8
post #2

Async will continue to look awkward until we drop explicit "await" on function calls. Awaiting should be the default. If I call "async foo()" then I get back a promise I can explicitly await, but there should be no semantic difference between calling a synchronous function or asynchronous without modifiers. Not only it makes it more consistent, but you can implement running a synchronous function into a separate thre…

What you are describing is really “async-never.” And in fact, the programming model that async/await gives you is in fact, blocking. To the programmer, awaiting is no different from blocking a function.

In practice though, async/await is a specific way to implement a blocking programming model with non-blocking internals. It basically breaks functions down into state machines so they can be executed incrementally. The other way is basically lightweight threads with semi-cooperative scheduling, like Go does, and what you could do in Lua (though Lua cothreads are technically quite different and their own bag of worms.)

This model works nicely in Go, whose lightweight threads pay the cost of C interop in some nanoseconds of overhead, and whose compiler adds yields in key points to aid platforms that can’t (yet) support some form of usermode preemption.

However, in JS, we already have the event loop. Control always returns back to the event loop. This has worked this way for decades in most places where JS is used, so it’s unlikely to change soon. Because of this, functions in JS can’t block. async/await is ideal here because the decomposed functions can return to the event loop when they await, for a callback to trigger the next fragment.

And in Rust, the language has use cases and environments that it runs in where the runtime costs and requirements of a lightweight thread scheduler are not acceptable. So yet again, async/await is ideal, because it allows for a blocking programming model that doesn’t compromise on the other harder requirements.

So then, should all functions just be “async”, and all function calls be “awaited?” I think this will prove complex and still undesirable. For one thing, async is hard in statically compiled languages. Your state will generally be the sum of the state of functions you await, which proves very complex for say, recursive async functions. Even when not, async functions require some kind of scheduler that manages all of the running routines. In a low level language, you may not want to impose an async routine scheduler or even async/await in the first place onto applications.

It’s unfortunate, but I think we’re finding at least local maxima to the solutions to async programming, and running into limits that appear harder to conquer. A blocking programming model is convenient, but if you want a blocking programming model with non-blocking behavior, it can’t come entirely “free.”

Re: Asynchronous Everything (2015)

#9
post #2

Async will continue to look awkward until we drop explicit "await" on function calls. Awaiting should be the default. If I call "async foo()" then I get back a promise I can explicitly await, but there should be no semantic difference between calling a synchronous function or asynchronous without modifiers. Not only it makes it more consistent, but you can implement running a synchronous function into a separate thre…

Isn’t this like saying Haskell’s monads are awkward and should be implicit?
Post reply on HN