A JavaScript Promise is an IOU for a value you’ll get later—perfect for API calls, timers, and other async work. In this quick guide, you’ll learn what a javascript promise is, how it behaves, and simple ways to use it in real projects.
New to fundamentals too? Check out my post on JavaScript scope and this beginner-friendly guide to Emmet productivity tips.
What is a JavaScript Promise?
A JavaScript Promise has three states: pending, fulfilled, and rejected. Instead of blocking the page, you attach handlers that run when the result arrives or fails. (Great background reading: MDN on Promises.)
const fetchUser = (id) =>
new Promise((resolve, reject) => {
setTimeout(() => id ? resolve({ id, name: "Riad" }) : reject(new Error("No ID")), 300);
});
How to use a JavaScript Promise with then/catch/finally
fetchUser(1)
.then(user => console.log("User:", user)) // success
.catch(err => console.error("Error:", err)) // failure
.finally(() => console.log("Done")); // always
Use this when you want quick chaining or you’re not inside an async function. See also MDN’s guide to then() and catch().
Async/Await: a friendlier way to work with JavaScript Promises
async function load() {
try {
const user = await fetchUser(1);
console.log(user.name);
} catch (e) {
console.error(e.message);
}
}
load();
Reads top-to-bottom like normal code but stays non-blocking. More at MDN: async/await.
Run Promises in parallel with Promise.all
const p1 = fetchUser(1);
const p2 = fetchUser(2);
const [u1, u2] = await Promise.all([p1, p2]);
console.log(u1, u2);
For other patterns, see MDN on Promise.all, allSettled, race, any.
JavaScript Promise Common pitfalls (and quick fixes)
- Always add a
.catch()or wrapawaitintry/catchto avoid unhandled rejections. - In
.then()chains, return the next value/Promise so the chain receives it. - Avoid mixing callbacks with Promises—wrap callbacks or use APIs that already return Promises.
Real-world: fetch with timeout (AbortController)
Network calls are where a JavaScript Promise shines. Add a timeout so the UI never hangs:
async function fetchJSON(url, { timeout = 5000, signal } = {}) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
// allow an external signal to abort too
if (signal) signal.addEventListener('abort', () => controller.abort());
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} finally {
clearTimeout(timer);
}
}
// Usage
fetchJSON('/api/users', { timeout: 3000 })
.then(data => console.log(data))
.catch(err => console.error('Request failed:', err.message));
Why this helps: you control failure cases explicitly instead of waiting forever.
Retry with simple backoff (robustifying a JavaScript Promise)
Transient failures happen. Wrap calls in a tiny retry helper:
async function retry(fn, { tries = 3, delay = 400 } = {}) {
let lastErr;
for (let i = 1; i <= tries; i++) {
try { return await fn(); }
catch (err) {
lastErr = err;
if (i < tries) await new Promise(r => setTimeout(r, delay * i)); // linear backoff
}
}
throw lastErr;
}
// Usage
retry(() => fetchJSON('/api/profile')).then(renderProfile).catch(showError);
Microtasks vs macrotasks (why .then feels “so fast”)
.then callbacks run in the microtask queue, which flushes before timers/DOM events. This explains some ordering surprises:
console.log('A');
Promise.resolve().then(() => console.log('B (microtask)'));
setTimeout(() => console.log('C (macrotask)'), 0);
console.log('D');
// Order: A, D, B, C
Knowing this helps debug “why did my handler run first?” moments.
When not to use a Promise alone
A JavaScript Promise doesn’t make CPU-heavy work faster—it just schedules results. For big computations, move work to a Web Worker (or a server) and optionally return a Promise that resolves when the worker posts back. Promises orchestrate; workers do the heavy lifting.
Quick combinator cheat sheet
Promise.all— fail fast if any rejects; great for loading independent data together.Promise.allSettled— never throws; inspect each result’s{status, value|reason}.Promise.race— first to settle wins (fulfill or reject).Promise.any— first to fulfill wins; rejects only if all reject.
Debugging tips
- Always end chains with a
.catch()during development—surface errors early. - In
asyncfunctions, log the stack:console.error(err.stack)gives better traces. - Name your functions (
async function getUser() {}) so stack traces are readable. - Prefer one style per block (either chain or
async/await) to avoid confusion.
Key takeaways
- JavaScript Promises model a future value.
- Use
then/catch/finallyfor quick chains; preferasync/awaitfor readability. - Reach for
Promise.allwhen tasks can run in parallel.
Internal reading:
External references:
Leave a Reply