Promises represent a value that may be available now, later, or never. async/await is syntax sugar over Promises that makes asynchronous code look synchronous, while preserving microtask scheduling and error propagation via rejections.
Promises and async/await
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
The Core Idea
A Promise is a container for an eventual result: pending → fulfilled (resolved) or pending → rejected. async/await makes working with Promises easier: await pauses within an async function until the Promise settles, then returns the value or throws the rejection.
Concept | What it means | Example |
|---|---|---|
Promise states | pending → fulfilled / rejected |
|
Thenables | Anything with | Libraries often return thenables |
Microtasks | Promise callbacks ( | Run after current stack, before timers |
async | Marks a function that always returns a Promise |
|
await | Pauses inside |
|
// Basic Promise
function getUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve({ id, name: 'John' }), 200);
});
}
getUser(1)
.then(u => u.name)
.then(name => console.log(name))
.catch(err => console.error('Error:', err))
.finally(() => console.log('done'));
// Equivalent with async/await
async function main() {
try {
const u = await getUser(1); // waits for fulfillment
console.log(u.name);
} catch (err) {
console.error('Error:', err); // rejections become throw
} finally {
console.log('done');
}
}
main();
Sequential vs Parallel
Using await inside a loop can serialize work unintentionally. Create Promises first, then await Promise.all for parallelism.
// ❌ Sequential (slow)
for (const url of urls) {
const res = await fetch(url);
results.push(await res.json());
}
// ✅ Parallel (fast)
const promises = urls.map(url => fetch(url).then(r => r.json()));
const results = await Promise.all(promises);
Promise combinators
API | Settles When | Behavior |
|---|---|---|
| All fulfill or one rejects | Fast-fail; rejects on first rejection |
| All settle | Never rejects; returns statuses |
| First settles | Adopts winner's state/value |
| First fulfillment | Rejects only if all reject (AggregateError) |
Error handling
With Promises, use .catch or the second arg of .then. With async/await, use try/catch. Unhandled rejections can crash Node.js (depending on version) or show as console warnings in browsers.
// Handling errors: async/await
async function load() {
try {
const data = await fetch('/api').then(r => {
if (!r.ok) throw new Error('HTTP ' + r.status);
return r.json();
});
return data;
} catch (e) {
// handle or rethrow
throw e;
}
}
Common pitfalls
- Forgetting to
returninside.thenchains → next.thengetsundefined. - Using
awaitinArray.prototype.forEach(it won’t await). Preferfor...oforPromise.all. - Assuming
asyncfunctions throw synchronously — they return a rejected Promise instead. - No built-in cancellation for Promises (use
AbortControllerwithfetchor libraries that support cancel).
// from callback to Promise
function readFileP(path) {
const fs = require?.('fs'); // works in Node
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err); else resolve(data);
});
});
}
Think of a Promise as an order ticket 🧾: you place the order (create Promise), you’ll be notified when it’s ready (microtask). With await, you stand by the counter for just that ticket without blocking other customers — when it’s ready, you pick it up and continue.
- Promises model eventual results and schedule callbacks as microtasks.
async/awaitis Promise syntax sugar: clearer flow, same semantics.- Prefer parallel with
Promise.all; avoid accidental serialization. - Use
try/catch(or.catch) and pick the right combinator for your needs.