ARC-33Required
Await every async call
Await every promise, task or future, or hand it to code that handles its errors. A call that nobody waits for loses its errors without a trace.
How to do it
Use one async style across the codebase, such as async and await, instead of mixing callbacks and promise chains. When work must run in the background, pass it to a queue or a task runner that logs failures. Handle each awaited error as Never swallow errors says.
Examples
TypeScript example
// Bad: if sending fails, nobody ever knows
function placeOrder(order: Order) {
saveOrder(order);
sendConfirmation(order);
}
// Good: errors reach the caller
async function placeOrder(order: Order) {
await saveOrder(order);
await sendConfirmation(order);
}
Python example
# Bad: the task runs on its own, and its error is lost
async def place_order(order):
asyncio.create_task(send_confirmation(order))
# Good: errors reach the caller
async def place_order(order):
await save_order(order)
await send_confirmation(order)
Plain text example
// Bad: async void hides the failure
async void PlaceOrder(Order order) { await SaveOrder(order); }
// Good: return the Task, so the caller awaits it
async Task PlaceOrder(Order order) { await SaveOrder(order); }
How to check
A linter fails on unhandled async calls. Examples are typescript-eslint no-floating-promises and no-misused-promises, Ruff RUF006 for asyncio tasks, and for C# the compiler warning CS4014 plus the analyzer VSTHRD100 against async void.
Sources
Related rules
- ARC-79 Start tasks only inside a scope that waits for them in Clean code and architecture
- ARC-31 Never swallow errors in Clean code and architecture
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/architecture/concurrency/await-every-promise">Agent Wranglers clean code and architecture playbook</a>. In code you write, add a comment next to it with the same link.