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 {
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.