Promises and Async/Await
Master asynchronous flow in JavaScript. Understand states, chaining promises, async-await keywords, error propagation, and Promise.all.
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is a Promise in JavaScript and what states can it have?
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It acts as a placeholder for a value that isn't available yet.
| State | Meaning |
|---|---|
| Pending | Initial state, operation not yet completed |
| Fulfilled | Operation completed successfully, has a resolved value |
| Rejected | Operation failed, has a reason/error |
// Creating a promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
resolve('Operation succeeded');
} else {
reject('Operation failed');
}
}, 1000);
});
console.log(myPromise); // Promise { <pending> }
myPromise
.then(result => console.log(result)) // 'Operation succeeded' after 1s
.catch(error => console.log(error));
// A promise can only settle once - either fulfilled or rejected, never both
const p = new Promise((resolve, reject) => {
resolve('first');
reject('second'); // ignored, promise already settled
});
p.then(val => console.log(val)); // 'first'
// Once settled, state and value are locked in permanently
const settled = Promise.resolve(42);
settled.then(val => console.log(val)); // 42, every time it's used
Q2. How do .then(), .catch(), and .finally() work in Promise chains?
These methods let you handle a promise's eventual result. Each returns a new Promise, which is what enables chaining.
| Method | Runs when | Purpose |
|---|---|---|
| .then() | Promise fulfills | Handle success value, can chain more logic |
| .catch() | Promise rejects (anywhere upstream) | Handle errors |
| .finally() | Always, regardless of outcome | Cleanup logic (hide spinner, close connection) |
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) resolve({ id, name: 'John' });
else reject(new Error('Invalid ID'));
}, 500);
});
}
fetchUser(1)
.then(user => {
console.log('User:', user);
return user.id; // returned value passed to next .then()
})
.then(id => {
console.log('ID was:', id);
})
.catch(error => {
console.log('Error:', error.message); // catches errors from ANY step above
})
.finally(() => {
console.log('Request finished'); // always runs
});
// A .catch() placed early stops error propagation for subsequent .then()
fetchUser(-1)
.then(user => console.log(user)) // skipped due to rejection
.catch(error => {
console.log('Caught:', error.message);
return 'default user'; // recovers the chain
})
.then(value => console.log('Continues with:', value)); // runs normally
Q3. How does async/await simplify working with Promises?
async/await is syntactic sugar over Promises that lets asynchronous code be written and read like synchronous code, avoiding long .then() chains.
// Promise chain version
function getUserData(id) {
return fetchUser(id)
.then(user => fetchOrders(user.id))
.then(orders => {
console.log('Orders:', orders);
return orders;
})
.catch(error => console.log('Error:', error));
}
// async/await version - reads top to bottom, like sync code
async function getUserData(id) {
try {
const user = await fetchUser(id);
const orders = await fetchOrders(user.id);
console.log('Orders:', orders);
return orders;
} catch (error) {
console.log('Error:', error);
}
}
// Key facts:
// 1. 'async' before a function makes it always return a Promise
async function greet() {
return 'Hello'; // automatically wrapped: Promise.resolve('Hello')
}
greet().then(msg => console.log(msg)); // Hello
// 2. 'await' can only be used inside an async function
// 3. 'await' pauses execution until the Promise settles, without blocking the thread
async function demo() {
console.log('Start');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('After 1 second'); // runs after pause, other code can run meanwhile
}
demo();
console.log('This runs before "After 1 second"');
Q4. How do you handle errors with async/await?
Errors in async/await are handled with standard try/catch blocks, unlike Promise chains which use .catch(). A rejected awaited Promise throws inside the try block.
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.log('Something went wrong:', error.message);
return null; // fallback value
} finally {
console.log('Fetch attempt finished'); // always runs
}
}
// Handling errors from multiple awaits individually
async function processOrder(orderId) {
let order;
try {
order = await getOrder(orderId);
} catch (error) {
console.log('Could not fetch order:', error.message);
return;
}
try {
await processPayment(order);
} catch (error) {
console.log('Payment failed:', error.message);
await refundIfNeeded(order);
}
}
// Common mistake: forgetting try/catch causes unhandled rejection
async function risky() {
const data = await fetch('/might-fail'); // if this rejects, function throws silently
return data;
}
risky().catch(err => console.log('Caught outside:', err)); // still catchable via .catch() on the call
Q5. What is the difference between Promise.all(), Promise.race(), Promise.allSettled(), and Promise.any()?
| Method | Resolves when | Rejects when |
|---|---|---|
| Promise.all() | All promises fulfill | Any one rejects (fails fast) |
| Promise.race() | First promise settles (fulfilled or rejected) | First settled is a rejection |
| Promise.allSettled() | All promises settle, regardless of outcome | Never rejects |
| Promise.any() | First promise fulfills | All promises reject |
const p1 = Promise.resolve(1);
const p2 = new Promise(res => setTimeout(() => res(2), 100));
const p3 = Promise.reject('error');
// Promise.all - fails fast on first rejection
Promise.all([p1, p2])
.then(results => console.log(results)); // [1, 2]
Promise.all([p1, p3])
.catch(err => console.log('Failed:', err)); // Failed: error
// Promise.race - first to settle wins
Promise.race([p1, p2])
.then(result => console.log('First:', result)); // First: 1
// Promise.allSettled - always resolves with status of each
Promise.allSettled([p1, p3]).then(results => {
console.log(results);
// [{status:'fulfilled', value:1}, {status:'rejected', reason:'error'}]
});
// Promise.any - first fulfillment, ignores rejections unless all fail
Promise.any([p3, p2])
.then(result => console.log('First success:', result)); // First success: 2
// Practical use case - fetch data from multiple APIs concurrently
async function loadDashboard() {
const [users, orders, stats] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/orders').then(r => r.json()),
fetch('/api/stats').then(r => r.json())
]);
return { users, orders, stats };
}
Q6. How do you run asynchronous operations in parallel vs sequentially using async/await?
Using await one after another runs operations sequentially (each waits for the previous). Starting all promises first and awaiting together runs them in parallel.
function delay(value, ms) {
return new Promise(resolve => setTimeout(() => resolve(value), ms));
}
// SEQUENTIAL - takes 3 seconds total (1+1+1)
async function sequential() {
console.time('sequential');
const a = await delay('A', 1000);
const b = await delay('B', 1000);
const c = await delay('C', 1000);
console.timeEnd('sequential'); // ~3000ms
return [a, b, c];
}
// PARALLEL - takes 1 second total (all run concurrently)
async function parallel() {
console.time('parallel');
const promiseA = delay('A', 1000); // starts immediately, not awaited yet
const promiseB = delay('B', 1000); // starts immediately too
const promiseC = delay('C', 1000); // starts immediately too
const results = await Promise.all([promiseA, promiseB, promiseC]);
console.timeEnd('parallel'); // ~1000ms
return results;
}
// Use sequential when operations DEPEND on each other
async function dependent() {
const user = await getUser(1); // must finish first
const orders = await getOrders(user.id); // needs user.id
return orders;
}
// Use parallel when operations are INDEPENDENT
async function independent() {
const [users, products] = await Promise.all([
getUsers(), // doesn't depend on products
getProducts() // doesn't depend on users
]);
return { users, products };
}
Q7. What is a microtask and how do Promises relate to the event loop?
Promise callbacks (.then, .catch, .finally, and code after await) are queued as microtasks, which run after the current synchronous code finishes but before the next macrotask (like setTimeout).
console.log('1: Sync start');
setTimeout(() => console.log('2: Macrotask (setTimeout)'), 0);
Promise.resolve().then(() => console.log('3: Microtask (Promise)'));
console.log('4: Sync end');
// Output order:
// 1: Sync start
// 4: Sync end
// 3: Microtask (Promise)
// 2: Macrotask (setTimeout)
// Explanation: All synchronous code runs first.
// Then ALL queued microtasks run before the next macrotask.
// Even setTimeout(fn, 0) waits behind every pending microtask.1. Run all synchronous code (call stack) → 2. Drain the entire microtask queue (Promises, queueMicrotask) → 3. Run one macrotask (setTimeout, setInterval, I/O) → 4. Repeat from step 2
// Nested microtasks also run before macrotasks
setTimeout(() => console.log('macrotask'), 0);
Promise.resolve()
.then(() => console.log('microtask 1'))
.then(() => console.log('microtask 2'));
// Output: microtask 1, microtask 2, macrotask
Q8. How do you convert a callback-based function into a Promise-based one?
Wrapping a callback-style function in a new Promise, calling resolve on success and reject on error, is called 'promisifying'. Node.js also provides util.promisify for this.
// Callback-style function (Node.js style: error-first callback)
function readFileCallback(path, callback) {
fs.readFile(path, 'utf8', (err, data) => {
if (err) callback(err);
else callback(null, data);
});
}
// Manually promisifying
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
// Usage with async/await
async function main() {
try {
const content = await readFilePromise('file.txt');
console.log(content);
} catch (error) {
console.log('Error:', error);
}
}
// Using Node's built-in util.promisify
const util = require('util');
const fs = require('fs');
const readFileAsync = util.promisify(fs.readFile);
async function main2() {
const content = await readFileAsync('file.txt', 'utf8');
console.log(content);
}
// Generic promisify helper for any error-first callback function
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
}
const readFileAsync2 = promisify(fs.readFile);
Q9. Can you use await inside a regular for loop and a forEach loop? What's the difference?
await works correctly inside a regular for loop because the loop itself is inside an async function's synchronous flow. It does NOT work as expected inside Array.forEach() because the callback passed to forEach is not awaited by forEach itself.
function delay(value, ms) {
return new Promise(resolve => setTimeout(() => resolve(value), ms));
}
// CORRECT - regular for loop, awaits sequentially
async function withForLoop() {
const items = [1, 2, 3];
for (const item of items) {
const result = await delay(item, 500);
console.log(result); // 1, 2, 3 in order, with real delay between
}
console.log('Loop complete');
}
// INCORRECT - forEach does not wait for the async callback
async function withForEach() {
const items = [1, 2, 3];
items.forEach(async (item) => {
const result = await delay(item, 500);
console.log(result); // Fires all together, order not guaranteed to complete first
});
console.log('Loop complete'); // This logs IMMEDIATELY, before any delay finishes
}
// CORRECT alternative to run in parallel: map + Promise.all
async function withMapAndAll() {
const items = [1, 2, 3];
const promises = items.map(item => delay(item, 500));
const results = await Promise.all(promises);
console.log(results); // [1, 2, 3] after ~500ms total, not 1500ms
}
// for...of DOES respect await because it's a normal loop construct,
// not a higher-order function taking a callback
Q10. What are common mistakes developers make with Promises and async/await?
| Mistake | Problem | Fix |
|---|---|---|
| Forgetting to return a promise in a chain | Next .then() gets undefined instead of resolved value | Always return inside .then() callbacks |
| Mixing async/await with .then() unnecessarily | Confusing, error-prone code | Pick one style consistently |
| Not handling rejections | Unhandled promise rejection warnings/crashes | Always use .catch() or try/catch |
| Awaiting sequentially when parallel is possible | Unnecessary slowdown | Use Promise.all() for independent operations |
// MISTAKE 1: Forgetting to return in .then()
fetchUser(1)
.then(user => {
fetchOrders(user.id); // missing 'return' - not chained properly
})
.then(orders => {
console.log(orders); // undefined, ran before fetchOrders resolved
});
// FIX
fetchUser(1)
.then(user => {
return fetchOrders(user.id); // properly chained
})
.then(orders => {
console.log(orders); // correct data
});
// MISTAKE 2: Unhandled rejection
async function risky() {
const data = await fetch('/might-404');
return data;
}
risky(); // if this rejects, it's an unhandled promise rejection
// FIX
risky().catch(err => console.log('Handled:', err));
// MISTAKE 3: Sequential awaits for independent operations
async function slow() {
const a = await taskA(); // waits
const b = await taskB(); // waits again unnecessarily
return [a, b];
}
// FIX
async function fast() {
const [a, b] = await Promise.all([taskA(), taskB()]);
return [a, b];
}
// MISTAKE 4: Creating a new Promise around an already-async function
// (the 'Promise constructor antipattern')
function wrapped() {
return new Promise((resolve, reject) => {
someAsyncFn().then(resolve).catch(reject); // unnecessary wrapping
});
}
// FIX - just return the promise directly
function simple() {
return someAsyncFn();
}
Promises and Async/Await
Master asynchronous flow in JavaScript. Understand states, chaining promises, async-await keywords, error propagation, and Promise.all.
What is a Promise in JavaScript and what states can it have?
A Promise is an object representing the eventual completion or failure of an asynchronous operation. It acts a...
How do .then(), .catch(), and .finally() work in Promise chains?
These methods let you handle a promise's eventual result. Each returns a new Promise, which is what enables ch...
How does async/await simplify working with Promises?
async/await is syntactic sugar over Promises that lets asynchronous code be written and read like synchronous...
How do you handle errors with async/await?
Errors in async/await are handled with standard try/catch blocks, unlike Promise chains which use .catch(). A...
What is the difference between Promise.all(), Promise.race(), Promise.allSettled(), and Promise.any()?
MethodResolves whenRejects whenPromise.all()All promises fulfillAny one rejects (fails fast)Promise.race()Firs...
How do you run asynchronous operations in parallel vs sequentially using async/await?
Using await one after another runs operations sequentially (each waits for the previous). Starting all promise...
What is a microtask and how do Promises relate to the event loop?
Promise callbacks (.then, .catch, .finally, and code after await) are queued as microtasks, which run after th...
How do you convert a callback-based function into a Promise-based one?
Wrapping a callback-style function in a new Promise, calling resolve on success and reject on error, is called...
Can you use await inside a regular for loop and a forEach loop? What's the difference?
await works correctly inside a regular for loop because the loop itself is inside an async function's synchron...
What are common mistakes developers make with Promises and async/await?
MistakeProblemFixForgetting to return a promise in a chainNext .then() gets undefined instead of resolved valu...