▲ | serbuvlad 7 days ago | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
As someone who has only written serious applications in single-threaded, or manually threaded C/C++, and concurrent applications in go using goroutines, channels, and all that fun stuff, I always find the discussion around async/await fascinating. Especially since it seems to be so ubiquitous in modern programming, outside of my sphere. But one thing is: I don't get it. Why can't I await in a normal function? await sounds blocking. If async functions return promises, why can't I launch multiple async functions, then await on each of them, in a non-async function that does not return a promise? I get there are answers to my questions. I get await means "yeald if not ready" and if the function is not async "yeald" is meaningless. But I find it a very strange way of thinking nonetheless. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | tbrownaw 3 days ago | parent | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The `await` keyword means "turn the rest of this function into a callback for the when the Task I'm waiting on finishes, and return the resulting Task". Returning a Task only works if your function is declared to return a Task. The `async` keyword flags functions that are allowed to be transformed like that. I assume it could have been made implicit. You can do a blocking wait on a Task or collection of Tasks. But you don't want to do that from a place that might be called from the event loop's thread pool (such as anything called from a Task's completion callback), since it can lock up. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | mrkeen 7 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
You can: https://hackage.haskell.org/package/async-2.2.5/docs/Control... As long as you don't mind - what did the article say? - >> transcending to a higher plane and looking down to the folks who are stitching together if statements, for loops, make side effects everywhere, and are doing highly inappropriate things with IO. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | skybrian 2 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Ultimately, it's because of how JavaScript models time. In a browser, JavaScript has a single event queue, and events are delivered one at a time. Event delivery is supposed to be fast. Conceptually, handling one event can be seen as a single tick of a clock, and practically, it should be less than one animation frame, so the UI doesn't freeze. If something takes time, you need to split it up over multiple events, so that it spans multiple clock ticks. An async function call is one that can return in a different clock tick. A "normal" JavaScript function call always returns in the same clock tick, and if it takes too long, it makes the clock tick take longer, holding everything up until it's done. This is a logical consequence of JavaScript starting out single-threaded and being used to write UI event handlers. In other languages, there isn't this distinction. Assumptions about event handling aren't baked so deeply into the language, so normal function calls in other languages don't make this guarantee about happening atomically. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | shepherdjerred 3 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
At least in JavaScript, you could mark all of your functions as `async`. This would mean that function would have to return a Promise and go back to the event loop which would add overhead. I imagine it'd kill performance since you'd essentially be context switching on every function call. The obvious workaround for this is to say "I want some of my code to run serially without promises", which is essentially is asking for a `sync` keyword (or, `async` which would be the inverse). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | AlienRobot 3 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
In my experience with web browsers, you can't do this because Javascript can NEVER block. For example, if a function takes too long to run, it blocks rendering of the page. If there were ways to make Javascript asynchronously, browsers would have implemented it already, so I assume they can't do it without potential backward incompatibility. One exception is alert(), which blocks and shows a dialog. But I don't think I've ever seen a website use it instead of showing a "normal" popup with CSS. It looks ugly so it's only used to debug that code actually runs. I'm not knowledgeable about low-level interruptions, but I think you would need at least some runtime code to implement blocking the thread. In any case, even if the language provides this, you can't use it because the main thread is normally a GUI thread that can't respond to user interaction if it's blocked by another thread. That's the main point of using (background) threads in the first place: so the main thread never blocks from IO bottlenecks. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | davnicwil 2 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
I get it - you'd like await semantics in a function without having to expose that detail to the caller. You can't get it directly in javascript but you're only one step away. Just not awaiting your function in the caller 'breaks the chain' so to speak, so that at least the caller doesn't have to be async. That way you can avoid tagging your function as async completely. Therefore one syntax workaround while still being able to use await semantics would just be to nest this extra level inside your function -- wrap those await calls in an anonymous inner function which is tagged async, which you just instantly call that without await, so the function itself doesn't have to be (and does not return a promise). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | binary132 3 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
I found it all very confusing until I eventually wrote a little async task scheduler in Lua. Lua has an async / cooperative-coroutine API that is both very simple and easy to express meaningful coroutines with. The API is almost like a sort of system of safer gotos, but in practice it’s very much like Go channel receives, if waiting for a value from a channel was how you passed control to the producer side, and instead of a channel, the producer was just a function call that would return the next value every time you passed it control. What’s interesting is that C++20 coroutines have very nearly the same API and semantics as Lua’s coroutines. Still haven’t taken the time to dive into that, but now that 23 is published and has working ranges, std::generator looks very promising since it’s kind of a lazy bridge between coroutines and ranges. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | Rohansi 3 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
`await` is only logically blocking. Internally the code in an async function is split up between each `await` so that each fragment can be called separately. They are cooperatively scheduled so `await` is sugar for 1) ending a fragment, 2) registering a new fragment to run when X completes, and 3) yielding control back to the scheduler. None of this internal behavior is present for non-async functions - in C# they run directly on bare threads like C++. Go's goroutines are comparable to async/await but everything is transparent. In that case it's managed by the runtime instead of a bit of syntactic sugar + libraries. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | akira2501 3 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> Why can't I await in a normal function? You can. promise.then(callback). If you want the rest of your logic to be "blocking" then the rest of it goes in the callback. the 'then' method itself returns a promise, so you can return that from a non async function, if you like. > why can't I launch multiple async functions, then await on each of them, in a non-async function that does not return a promise? Typically? Exception handling semantics. See the difference between Promise.race, Promise.all and Promise.allSettled. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | mr_coleman 6 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
In C# you can do a collection of Task<T>, start them and then do a Task.WaitAll() on the collection. For example a batch of web requests at the same time and then collect the results once everything is done. I'm not sure how it's done in other languages but I imagine there's something similar. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | avandekleut 7 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
At least in node, its because the runtime is an event loop. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | MatmaRex 3 days ago | parent | prev | next [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
You can await in a normal function in better languages, just not in JavaScript. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
▲ | chrisweekly 3 days ago | parent | prev [-] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
yeald -> yield |