Sequential vs parallel

// SLOW — sequential (each awaits the previous)
const user  = await getUser(id);
const orders = await getOrders(id);

// FAST — parallel (both fire simultaneously)
const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);

Error handling

// Always handle rejections — unhandled promise rejections crash processes
try {
  const result = await riskyOperation();
} catch (err) {
  logger.error({ err }, "Operation failed");
  throw new AppError("OPERATION_FAILED", err.message);
}

Avoiding the async leak

Do not make a function async unless you are actually awaiting something inside it. Unnecessary async wrapping adds overhead and makes stack traces harder to read.

Async iterators for streaming

for await (const chunk of streamLargeFile(path)) {
  await processChunk(chunk);
}
// Processes each chunk as it arrives without loading the whole file in memory

Timeout pattern

const withTimeout = (promise, ms) => Promise.race([
  promise,
  new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), ms))
]);