3 ms·
> Async code bubbles all the way to the top. If you want to use await, then you have to mark your function as async. Then if someone else calling your function
by thwarted 2y ago
> Async code bubbles all the way to the top. If you want to use await, then you have to mark your function as async. Then if someone else calling your function wants to use await, they also have to mark themselves as async, on and on until the root of the call chain. If at any point you don’t then you have to use the async result (in JavaScript’s case a Promise<T>).
I find many descriptions of async code to be confusing, and this kind of description is exactly why.
This description is backwards. You don't choose to use await and then decorate functions with async. Or maybe you do and that's why so many async codebases are a mess.
You don't want to block while a long running operation completes, so you decorate the function that performs that operation with async and return a Promise.
But Promises have exactly the same value as promises in the real world: none until they are fulfilled. You can't do further operations on a promise, you can only wait for it to be done, you have to wait for the promise to be fulfilled to get the result that you actually want to operate on.
The folly of relying on a promise is embodied in the character Whimpy from Popeye: "I'll gladly pay you Tuesday for a hamburger today".
Once you have a promise, you have to await on it, turning the async operation into a synchronous operation.
This example seems crazy to me:
async function readFile(): Promise<string> {
return await read();
}
This wraps what should be an async operation that returns a promise (read) in an expression that blocks (await read()) inside a function that returns a promise so you didn't need to block on it!. This is a useless wrapper. This kind of construct is probably the significant contribution to the mess: just peppering code with async and await and wrapper functions.
await is the point where an async operation is blocked on to get back into a synchronous flow. Creating promises means you ultimately need to block in a synchronous function to give the single threaded runtime a chance to make progress on all the promises. Done properly, this happens by the event loop. But setting that up requires the actual operation of all your code to be async and thus callback hell and the verbose syntactic salt to even express that in code.
That all being said, this piece is spot on. Threads (in general, but in ruby as the topic of this piece) and go's goroutines encapsulate all this by abstracting over the state management of different threads of execution via stacks. Remove the stacks and async programming requires you to manage that state yourself. Async programming removes a very useful abstraction.
Independent threads of execution, if they are operating system managed threads, operating system managed processes (a special case of OS managed threads), green threads, or go routines, are a scheduler abstraction. Async programming forces you to manage that scheduling. Which may be required if you don't also have an abstraction available for preemption, but async leaks the single threaded implementation into your code, and the syntactic salt necessary to express it.
- graypegg 2y agoTo be fair to the author, they do mention in the paragraph above that sample: Async code bubbles all the way to the top. If you want to use await, then you have to mark your function as async. [...] If at any point you don’t then you have to use the async result (in JavaScript’s case a Promise<T>). I think it's just an artificially lengthy example to show how the responsibility of working with promises grows up the callstack. Interpreting it that way since the final function they define in that sample is `iGiveUp` which is not using the async keyword, but returns a promise. Definitely could be made a bit more clear that's it's illustrative and not that the async keyword is somehow unlocking some super special runtime mode separate from it's Promise implementation.
- monkpit 2y ago> This example seems crazy to me […] To be fair, this is sort of pointless in JS/TS because an async function returns a promise type by default, so the return value has to be ‘await’ed to access the value anyways. There are linter rules you can use to ban ‘return await’. The only benefit to ‘return await…’ is when wrapped with a try/catch - you can catch if whatever you ‘await’ed throws an error.
- thwarted 2y agoWell, that was a simplistic contrived example, it may make sense to do this if you have other functionality you want to put in the wrapper. But I've seen more of these blind wrappers, the result of stuffing async and await keywords around in some misguided attempt to make things go faster, in code than is really warranted so it's not being made clear that examples like this are for explanatory purposes and shouldn't be cargo-culted (if anything should be cargo-culted).