Promises are the foundation of all modern JavaScript async code — and most developers only use 10% of the API. Creating promises, chaining correctly, handling errors without swallowing them, and knowing when to use Promise.all vs allSettled vs race vs any — these distinctions determine correctness and performance in production code. This guide focuses on JavaScript promises, with the production trade-offs senior engineers need to make sound decisions.
⚡ TL;DR: Promises have three states: pending, fulfilled, rejected. Chain with .then() for success, .catch() for errors. Promise.all fails fast. Promise.allSettled waits for all. Promise.race returns first settled. Promise.any returns first fulfilled. async/await is syntactic sugar over promises.
Creating promises from scratch — JavaScript promises
// Promise constructor: takes executor (resolve, reject)
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const fetchWithTimeout = (url, timeoutMs) => new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('Timeout')), timeoutMs);
fetch(url)
.then(res => { clearTimeout(timer); resolve(res); })
.catch(err => { clearTimeout(timer); reject(err); });
});
// Promisify callback-style functions
const { promisify } = require('util');
const readFile = promisify(require('fs').readFile);
await readFile('file.txt', 'utf8');Chaining — return value propagation
// Every .then() returns a new Promise
// Return value becomes next .then()'s input
fetch('/api/user')
.then(res => res.json()) // Returns Promise
.then(raw => normalize(raw)) // Returns UserNormalized
.then(user => saveToCache(user)) // Returns Promise
.catch(err => handleError(err)); // Catches ANY error above
// Mistake: forgetting to return inside .then()
fetch('/api/user')
.then(res => {
res.json(); // NO return! Next .then() gets undefined
})
.then(data => console.log(data)); // undefined
// Correct:
.then(res => res.json()) // Implicit return with arrow fn Promise combinators — all four
const p1 = fetch('/api/users');
const p2 = fetch('/api/posts');
const p3 = fetch('/api/settings');
// Promise.all: all succeed or fail together
const [users, posts, settings] = await Promise.all([p1, p2, p3]);
// If p2 rejects: immediately rejects, p1 and p3 results lost
// Promise.allSettled: wait for all, get individual results
const results = await Promise.allSettled([p1, p2, p3]);
results.forEach(r => {
if (r.status === 'fulfilled') use(r.value);
if (r.status === 'rejected') log(r.reason);
});
// Use when: partial success acceptable (dashboard widgets)
// Promise.race: first to settle wins
const result = await Promise.race([fetch(url), delay(5000).then(() => { throw new Error('timeout'); })]);
// Promise.any: first to FULFILL (ignores rejections)
const fastest = await Promise.any([serverA(), serverB(), serverC()]);Error handling — the right way
// WRONG: unhandled rejection
async function bad() {
const data = await riskyCall(); // Throws? Unhandled rejection!
}
// WRONG: catch that swallows
async function alsoBad() {
try {
return await riskyCall();
} catch (e) {} // Error silently swallowed!
}
// RIGHT: handle or re-throw
async function good() {
try {
return await riskyCall();
} catch (e) {
logger.error('riskyCall failed', e);
throw e; // Re-throw unless you can meaningfully recover
}
}
// Result pattern — explicit error as value
async function safe() {
try { return { ok: true, data: await riskyCall() }; }
catch (e) { return { ok: false, error: e.message }; }
}- ✅ Always return inside .then() callbacks
- ✅ Promise.allSettled when partial success is acceptable
- ✅ Promise.race for timeouts and hedged requests
- ✅ Promise.any for redundant sources — fastest wins
- ❌ Never swallow errors in .catch() without at minimum logging
- ❌ Never mix .then()/.catch() chains with async/await in the same function
Promises are the foundation of the async/await guide — async/await is syntax over promises. External reference: MDN Promise documentation.
Recommended Reading
→ Designing Data-Intensive Applications — The essential book every senior developer needs.
→ The Pragmatic Programmer — Timeless engineering wisdom for writing better code.
Affiliate links. We earn a small commission at no extra cost to you.
Free Weekly Newsletter
🚀 Don’t Miss the Next Cheat Code
Join 1,000+ senior developers getting expert JS, Python, AWS and system design secrets weekly.
Related: TypeScript for JavaScript Developers: The Migration Guide That Actually Works · JavaScript Closures, Scope, and the Module Pattern: The Complete Guide · JavaScript Array Methods: Every Method Explained With Real Examples
Related: JavaScript Array Methods: Every Method Explained With Real Examples · AWS DynamoDB: Advanced Patterns for Production at Scale · Design Patterns in JavaScript: The Gang of Four Patterns Every Senior Dev Uses
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
