JavaScript

Async/Await & Promises Best Practices

async/await makes asynchronous code read like synchronous code, which is exactly what makes it easy to forget it's still asynchronous underneath — leading to specific, recurring mistakes around error handling and concurrency.

Last updated 2026-09-08

1

Always wrap awaited code in try/catch, or handle rejection explicitly

An unhandled rejection from an awaited Promise behaves like a thrown error — without a surrounding try/catch (or a .catch() further up the call chain), it can crash a process or silently fail depending on the environment.

2

Run independent async operations concurrently with Promise.all, not sequentially

Awaiting several independent async calls one after another when they don't depend on each other's results wastes time unnecessarily — running them concurrently with Promise.all cuts total wait time to the slowest single operation instead of the sum of all of them.

3

Use Promise.allSettled when you need results even if some operations fail

Promise.all rejects immediately if any single promise rejects, discarding the results of ones that succeeded. Promise.allSettled always resolves, giving you the status and result/error of every operation, useful when partial success is acceptable.

4

Don't mix .then() chains with async/await unnecessarily

Combining both styles in the same function makes control flow harder to follow. Pick one style per function — mixing usually signals code that's been partially migrated and not finished.

5

Be deliberate about whether a loop should run sequentially or concurrently

await inside a for loop runs each iteration sequentially, which is correct when order matters but wastes time when iterations are independent — use Promise.all with .map() for independent concurrent work instead.

Common Mistakes to Avoid

  • Awaiting a Promise with no try/catch and no rejection handling
  • Running independent async calls sequentially with separate awaits instead of Promise.all
  • Using Promise.all when partial success matters, losing all results if one operation rejects
  • await inside a loop for genuinely independent operations, serializing work that could run concurrently
  • Forgetting that an async function always returns a Promise, even if its body has no explicit await

Frequently Asked Questions

Does an async function always return a Promise?

Yes, always — even an async function with no await inside it still returns a Promise, wrapping whatever value it returns. This surprises people expecting a plain value back from calling an async function directly.

What happens if I forget to await an async function call?

The call still executes, but you get back an unresolved Promise instead of its eventual result, and your code continues without waiting for it — a very common source of subtle timing bugs.