Promises and async/await patterns for asynchronous JavaScript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
resolve('Operation successful!');
} else {
reject('Operation failed!');
}
}, 1000);
});
// Consuming with .then() and .catch()
myPromise
.then(result => {
console.log(result);
return 'Next step';
})
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
})
.finally(() => {
console.log('Cleanup code');
});
// Real-world example: Fetch API
function fetchUserData(userId) {
return fetch(`https://api.example.com/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('User data:', data);
return data;
})
.catch(error => {
console.error('Fetch error:', error);
throw error;
});
}
// Async/Await - cleaner syntax
async function fetchUserDataAsync(userId) {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log('User data:', data);
return data;
} catch (error) {
console.error('Fetch error:', error);
throw error;
} finally {
console.log('Request completed');
}
}
// Sequential async operations
async function getFullUserProfile(userId) {
try {
const user = await fetchUserData(userId);
const posts = await fetchUserPosts(userId);
const comments = await fetchUserComments(userId);
return {
user,
posts,
comments
};
} catch (error) {
console.error('Error fetching profile:', error);
return null;
}
}
// Promise.all - parallel execution
async function getFullUserProfileParallel(userId) {
try {
const [user, posts, comments] = await Promise.all([
fetchUserData(userId),
fetchUserPosts(userId),
fetchUserComments(userId)
]);
return { user, posts, comments };
} catch (error) {
// If ANY promise fails, entire operation fails
console.error('Error:', error);
return null;
}
}
// Promise.allSettled - wait for all regardless of outcome
async function getAllUserData(userIds) {
const promises = userIds.map(id => fetchUserData(id));
const results = await Promise.allSettled(promises);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`User ${userIds[index]}:`, result.value);
} else {
console.error(`User ${userIds[index]} failed:`, result.reason);
}
});
return results;
}
// Promise.race - first to complete wins
async function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), timeout);
});
try {
const response = await Promise.race([fetchPromise, timeoutPromise]);
return await response.json();
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
// Promise.any - first fulfilled promise wins
async function fetchFromMultipleSources(urls) {
try {
const promises = urls.map(url => fetch(url).then(r => r.json()));
const data = await Promise.any(promises);
console.log('First successful response:', data);
return data;
} catch (error) {
console.error('All requests failed');
throw error;
}
}
// Creating utility functions
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function delayedGreeting(name) {
await delay(2000);
return `Hello, ${name}!`;
}
// Retry logic
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('HTTP error');
return await response.json();
} catch (error) {
console.log(`Attempt ${i + 1} failed`);
if (i === retries - 1) throw error;
await delay(1000 * (i + 1)); // Exponential backoff
}
}
}
// Processing array items sequentially
async function processItemsSequentially(items) {
const results = [];
for (const item of items) {
const result = await processItem(item);
results.push(result);
}
return results;
}
// Processing array items in parallel
async function processItemsParallel(items) {
const promises = items.map(item => processItem(item));
return await Promise.all(promises);
}
// Processing with concurrency limit
async function processWithLimit(items, limit = 3) {
const results = [];
const executing = [];
for (const item of items) {
const promise = processItem(item).then(result => {
executing.splice(executing.indexOf(promise), 1);
return result;
});
results.push(promise);
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
return await Promise.all(results);
}
// Chaining async operations
async function complexWorkflow(userId) {
try {
// Step 1: Fetch user
const user = await fetchUserData(userId);
console.log('Got user:', user.name);
// Step 2: Fetch user's posts
const posts = await fetchUserPosts(user.id);
console.log(`Got ${posts.length} posts`);
// Step 3: Process posts in parallel
const processedPosts = await Promise.all(
posts.map(post => enrichPostData(post))
);
// Step 4: Save results
await saveProcessedData(processedPosts);
return { user, posts: processedPosts };
} catch (error) {
console.error('Workflow failed:', error);
throw error;
}
}
// Converting callback to promise
function readFilePromise(filename) {
return new Promise((resolve, reject) => {
fs.readFile(filename, 'utf8', (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
// Usage
async function main() {
try {
const data = await readFilePromise('file.txt');
console.log(data);
} catch (error) {
console.error('Error reading file:', error);
}
}
// Promise chaining with error recovery
async function resilientFetch(url) {
return fetch(url)
.then(response => response.json())
.catch(error => {
console.warn('Primary source failed, trying backup');
return fetch('https://backup.example.com/data')
.then(r => r.json());
})
.catch(error => {
console.error('All sources failed');
return { error: 'Failed to fetch data' };
});
}
// Async iterator
async function* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
async function useAsyncGenerator() {
for await (const value of asyncGenerator()) {
console.log(value);
}
}
Promises represent eventual completion or failure of asynchronous operations. I create promises with new Promise((resolve, reject) => {}) executor function. The .then() method chains successful results while .catch() handles errors. Using .finally() runs cleanup code regardless of outcome. The async keyword declares asynchronous functions that return promises. Inside async functions, await pauses execution until promise resolves. I prefer async/await over promise chains for readability. The Promise.all() waits for multiple promises to resolve concurrently. Using Promise.race() returns first settled promise. The Promise.allSettled() waits for all promises regardless of outcome. Error handling with try/catch in async functions is more intuitive.