The async/await saga is a chapter-by-chapter story: callbacks introduced hell, promises introduced boilerplate, await cleaned the syntax but left function coloring and performance tradeoffs unresolved.
Key Takeaways
Each generation of async solved its predecessor’s most visible pain while introducing subtler costs at the next layer.
Function coloring – the forced split between async and sync call graphs – is the central structural cost async/await imposes on a codebase.
The piece frames async/await as a design pattern with real tradeoffs, not a universal concurrency solution, and traces how different ecosystems responded.
Go’s CSP model and Rust’s zero-cost state machines represent divergent answers to the same underlying concurrency problem async/await was solving.
Hacker News Comment Review
Commenters challenged the article’s thread-cost framing: Linux thread creation benchmarks closer to 10 microseconds, not milliseconds, weakening the core argument for preferring async over OS threads in most applications.
The discussion landed on a sharp language-by-language split: JS had no choice given its single-threaded runtime; C# async composes with cancellation tokens and a real thread pool; Rust async compiles to call-site state machines with zero runtime overhead – treating them as one pattern misrepresents all three.
Function coloring drew more nuance than the article implies: recoloring during refactors is low friction, but async on hot paths adds real latency via accumulated promise microtasks, making it a performance concern rather than just a maintenance one.
Notable Comments
@dcan: argues embedded Rust async is the genuinely compelling case – Embassy and RTIC make hardware abstractions like DMA-backed UART practical where OS threads aren’t available.
@SebastianKra: points out that async/await’s value in JS is partly about synchronous code clarity – the absence of await is an implicit signal that nothing will interleave with your computation, acting as a lightweight type-system contract.