Async Patterns & Callbacks
Move from callbacks to Promises and async/await while handling errors and concurrency safely.
Callbacks and Callback Hell
Early Node APIs use callbacks with error-first convention: (err, result) => void. Nested callbacks become hard to read and error handling duplicates at each level.
util.promisify converts callback-style functions to Promise-returning versions for fs and legacy libraries.
Recognize callbacks in older modules but write new code with async/await for clarity.
- Always handle err as first callback argument
- Avoid mixing callbacks and Promises in the same function
- Prefer fs/promises over promisify for built-in fs APIs
import { promisify } from 'node:util';
import { readFile } from 'node:fs';
const readFileAsync = promisify(readFile);
const data = await readFileAsync('file.txt', 'utf8');Promises and Composition
Promises represent a single async result. Chain with then/catch or use Promise.all for parallel work and Promise.allSettled when partial failure is acceptable.
Race timeouts with Promise.race and manual timer Promises to bound slow dependencies.
Wrap third-party event APIs with new Promise((resolve, reject) => ...) when no Promise interface exists.
- Return Promises from all async service methods
- Use Promise.allSettled for bulk operations with independent failures
- Avoid floating Promises; await or attach catch in entrypoints
const [user, orders] = await Promise.all([ fetchUser(id), fetchOrders(id), ]);
Async/Await Best Practices
async functions return Promises implicitly. await pauses within the function without blocking the event loop globally.
Use try/catch around await sequences for local error handling. For parallel tasks, await Promise.all rather than awaiting inside a loop sequentially unless order matters.
Top-level await works in ES modules for scripts and test files, simplifying bootstrap code.
- Use for...of with await when steps depend on previous results
- Use Promise.all with map for independent concurrent tasks
- Mark async route handlers and always forward errors to Express
async function createOrder(input) {
try {
const user = await validateUser(input.userId);
return await db.orders.insert({ ...input, userId: user.id });
} catch (err) {
logger.error(err);
throw new AppError('Order failed', { cause: err });
}
}