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))
]);