Do you actually know what happens when your bot hits an await? Not the textbook answer. The real one, in the specific language you shipped last week, with the specific runtime you picked because it had the nicest docs.
I ask because I’ve been building bots for years and I got this wrong for most of them. A paper landed in August 2026 from Gavin Gray, Shriram Krishnamurthi, and Will Crichton, presented at OOPSLA 2026, that maps out the design space of async/await across languages. They identify nine design dimensions that change how tasks behave and execute. Nine. Not one universal model with cosmetic syntax differences, which is roughly what I had assumed.
Straight-line asynchrony is the sales pitch
The whole point of async/await, and the reason it spread across so many languages, is straight-line asynchrony. You write code that reads top to bottom. No callback nesting, no promise chains that look like a fold-out map. The concurrency is there but it doesn’t wreck the shape of your function. That’s the promise: simplify concurrent programming.
For bot work, this is genuinely useful. A bot is mostly waiting. Waiting on an API, waiting on a model response, waiting on a webhook, waiting on a database that’s waiting on something else. Async/await lets you express “wait for this, then do that” without the code turning into a diagram.
The trouble is that straight-line reading encourages straight-line thinking. The code looks sequential, so you reason about it sequentially, and then the runtime does something that isn’t in your mental model at all.
Where the nine dimensions bite
The paper flags that key differences exist among languages, and two areas stand out as the ones that will actually cost you: task lifecycle and cancellation.
Task lifecycle is the question of when your async work starts. Does calling an async function begin the work immediately, or does nothing happen until you await it? This is not a trivia question. If your bot fires off three API calls intending them to run concurrently and the language defers all the work to the await point, you have written sequential code that looks concurrent. It’ll pass tests. It’ll be three times slower than you think under load, and you’ll blame the API.
Cancellation is worse, because it’s where the abstraction stops being polite. Say a user sends a follow-up message while your bot is mid-generation on the previous one. You want to cancel the in-flight work. What happens to the task? Does it stop at the next await point? Does it stop at all? Does cleanup code run? Does the task get a chance to finish something it started, like a partial write?
That last one is the trap for bot builders specifically. Our async functions tend to be full of side effects. We’re posting messages, updating conversation state, writing logs, incrementing counters. Cancel one halfway through and you don’t get a clean rollback, you get a bot that told a user something and then forgot it said it.
What I’m doing about it
The practical takeaway from a paper like this isn’t a new library. It’s a checklist. Before I trust async code in a bot that touches real users, I now want answers to a few things:
- When does the work start? At call time or at await time? Write a tiny script that proves it rather than trusting the docs.
- What does cancellation actually do in this language? Does it interrupt, or does it request politely and wait?
- Does my cleanup code run on cancellation? Test it. Don’t assume.
- What happens to a task nobody awaits? Silently dropped, logged, or an error?
- Where are my side effects relative to my await points? Anything between two awaits is a place where a cancelled task can leave the world half-changed.
That last point has changed how I structure bot handlers. I try to keep the side-effecting part of an operation as tight as possible, ideally with no await in the middle of it. Do all the waiting first, gather what you need, then commit the changes in one uninterrupted stretch. It’s not a new idea, it’s the same instinct as keeping transactions short, but async/await hides the danger well enough that the instinct doesn’t fire on its own.
Nine dimensions is a feature, not a bug
There’s a temptation to read work like this as an indictment of async/await. I don’t think that’s right. The nine dimensions exist because language designers made different tradeoffs for different reasons, and most of those reasons are defensible in context.
What’s not defensible is porting a mental model from one language to another and expecting it to hold. If you learned async in JavaScript and now you’re writing bots in Rust or Python or Swift, you are working with a different machine wearing the same keywords. The syntax transferred. The semantics did not.
Read the paper if you write async code for a living. Then go test your assumptions in your own runtime. Mine did not survive the experiment.
🕒 Published: