EN/CH Mode
BRUCE_FE JS Interview Notes - Promise & Async/Await In-depth Analysis
In-depth explanation of Promise and Async/Await mechanisms, usage, common methods and implementation in JavaScript to help you easily tackle related interview questions.
EN/CH Mode
Lazy to read articles? Then watch videos!
Promise Basic Concepts
Promise is the standard way to handle asynchronous operations in JavaScript, representing an operation that hasn't completed yet but is expected to in the future. A Promise has three states:
- •
Pending
: Initial state, neither fulfilled nor rejected - •
Fulfilled
: Operation completed successfully - •
Rejected
: Operation failed
const promise = new Promise((resolve, reject) => {
// Asynchronous operation
if (/* success */) {
resolve(value); // state changes to fulfilled
} else {
reject(error); // state changes to rejected
}
});
Why Do We Need Promises?
Promises solve several issues with traditional callbacks, providing a more elegant way to handle asynchronous operations:
- •
Avoiding callback hell
: Solves code maintainability issues caused by nested callbacks - •
Unified error handling
: Using the catch method to handle errors uniformly - •
Chaining
: Implementing clear flow control through the then method - •
Better semantics
: Promise state transitions are more intuitive for programming
// Traditional callback approach
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log(finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
// Promise approach
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(finalResult))
.catch(failureCallback);
Advantages of Async/Await
Async/Await is syntactic sugar built on top of Promises, providing a more intuitive way to handle asynchronous operations:
- •
Synchronous style
: Makes asynchronous code look like synchronous code - •
Error handling
: Can use traditional try/catch for error handling - •
Easier debugging
: Can debug line by line like synchronous code - •
Conditional handling
: Easier to implement complex conditional logic
async function fetchUserData() {
try {
const response = await fetch('/api/user');
const userData = await response.json();
return userData;
} catch (error) {
console.error('Error fetching user data:', error);
throw error;
}
}
Promise Static Methods
Promise.all()
Executes multiple Promises in parallel, waiting for all to complete. If any Promise fails, the entire operation fails.
Promise.all([
fetch('/api/users'),
fetch('/api/posts')
]).then(([users, posts]) => {
// process results
});
Promise.race()
Returns the result of the first completed Promise, regardless of success or failure.
Promise.race([
fetch('/api/data'),
new Promise((_, reject) =>
setTimeout(() => reject('Timeout'), 5000)
)
]);
Promise.any()
Returns the result of the first successful Promise. Only rejects when all Promises fail.
Promise.any([
fetch('https://api1.example.com'),
fetch('https://api2.example.com')
]).then(firstSuccess => {
// Handle the first successful result
});
Promise.allSettled()
Waits for all Promises to complete, returning the status of each Promise result.
Promise.allSettled([
fetch('/api/users'),
fetch('/api/posts')
]).then(results => {
// process all results
});
🔥 Common Interview Questions
(1) What are the state transition rules for Promises?
Answer: Promise state transitions have the following characteristics:
- •Initial state is Pending
- •Can only transition from Pending to Fulfilled or Rejected
- •State transitions are irreversible; once changed, they cannot be changed again
- •When state transitions occur, corresponding callbacks (then or catch) are triggered
(2) What's the difference between Promise.all and Promise.race?
Answer:
Feature | Promise.all | Promise.race |
---|---|---|
Completion condition | All Promises complete | Any Promise completes |
Failure handling | Fails if any Promise fails | Uses the first completed result |
Return value | Array of all results | Result of first completion |
(3) What's the relationship between async/await and Promises?
Answer:
- •
async/await
is syntactic sugar built on top of Promises - •
async
functions always return a Promise - •
await
can only be used inside async functions - •await can wait for any
'thenable'
object (objects that implement the then method)
// async functions always return a Promise
async function example() {
return "Hello";
}
// Equivalent to
function example() {
return Promise.resolve("Hello");
}
// Verify that async returns a Promise
console.log(example()); // Promise {<fulfilled>: "Hello"}
// await unwraps the value resolved by the Promise
async function example() {
const result = await example(); // Waits for Promise.resolve("Hello") result
console.log(result); // "Hello"
// Equivalent to
example().then(value => console.log(value)); // "Hello"
}
// Complete comparison example
// Promise approach
function fetchData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => data)
.catch(error => console.error(error));
}
// async/await approach - more readable synchronous style
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data; // automatically wrapped as Promise.resolve(data)
} catch (error) {
console.error(error);
}
}
(4) How to implement your own Promise.all?
Answer: We can implement a simple MyPromise.all function that mimics the functionality of Promise.all:
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
// If input is not an array, reject immediately
if (!Array.isArray(promises)) {
return reject(new TypeError('promises must be an array'));
}
const results = []; // Store all promise results
let completed = 0; // Number of completed promises
// If empty array, return empty array result
if (promises.length === 0) {
return resolve(results);
}
// Loop through all promises
promises.forEach((promise, index) => {
// Use Promise.resolve to handle non-Promise values
Promise.resolve(promise)
.then(result => {
results[index] = result; // Maintain original order
completed++;
// When all promises complete, resolve the final result
if (completed === promises.length) {
resolve(results);
}
})
.catch(error => {
// If any promise rejects, the entire myPromiseAll rejects
reject(error);
});
});
});
}
// Usage example
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = Promise.resolve(3);
myPromiseAll([p1, p2, p3])
.then(results => console.log(results)) // [1, 2, 3]
.catch(error => console.error(error));
Key points of this implementation:
- •Returns a new
Promise
- •Tracks completion status of all
Promise
s - •Maintains the original order of results
- •Immediately rejects the entire
Promise
when anyPromise
fails - •Uses
Promise.resolve
to handle non-Promise values