javascript
exercises
exercises.js⚡javascript
// ============================================
// 18.1 Promise Combinators - Exercises
// ============================================
// Helper function
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Exercise 1: Basic Promise.all
// Fetch three users in parallel and return their names
async function fetchUser(id) {
await delay(100);
return { id, name: `User ${id}` };
}
// Your code here:
// async function getAllUserNames(ids) {
// ...
// }
// Test: getAllUserNames([1, 2, 3]).then(console.log);
// Expected: ['User 1', 'User 2', 'User 3']
/*
Solution:
async function getAllUserNames(ids) {
const users = await Promise.all(ids.map(id => fetchUser(id)));
return users.map(user => user.name);
}
getAllUserNames([1, 2, 3]).then(console.log);
*/
// --------------------------------------------
// Exercise 2: Promise.all with Error Handling
// Fetch users but catch errors and return null for failed fetches
async function fetchUserMayFail(id) {
await delay(50);
if (id === 2) throw new Error('User not found');
return { id, name: `User ${id}` };
}
// Your code here:
// async function getAllUsersSafe(ids) {
// // Should return [user1, null, user3] if user 2 fails
// }
/*
Solution:
async function getAllUsersSafe(ids) {
const promises = ids.map(async id => {
try {
return await fetchUserMayFail(id);
} catch {
return null;
}
});
return Promise.all(promises);
}
getAllUsersSafe([1, 2, 3]).then(console.log);
// [{ id: 1, name: 'User 1' }, null, { id: 3, name: 'User 3' }]
*/
// --------------------------------------------
// Exercise 3: Using Promise.allSettled
// Fetch multiple URLs and return { successful: [...], failed: [...] }
async function fetchUrl(url) {
await delay(50);
if (url.includes('bad')) throw new Error(`Failed: ${url}`);
return { url, content: 'data' };
}
// Your code here:
// async function fetchAllUrls(urls) {
// // Return { successful: [...results], failed: [...errors] }
// }
// Test:
// fetchAllUrls(['/good1', '/bad', '/good2']).then(console.log);
/*
Solution:
async function fetchAllUrls(urls) {
const results = await Promise.allSettled(urls.map(url => fetchUrl(url)));
return {
successful: results
.filter(r => r.status === 'fulfilled')
.map(r => r.value),
failed: results
.filter(r => r.status === 'rejected')
.map(r => r.reason.message)
};
}
fetchAllUrls(['/good1', '/bad', '/good2']).then(console.log);
*/
// --------------------------------------------
// Exercise 4: Implement Timeout using Promise.race
// Create a function that rejects if operation takes too long
// Your code here:
// function withTimeout(promise, ms) {
// ...
// }
// Test:
// withTimeout(delay(100).then(() => 'fast'), 500).then(console.log); // 'fast'
// withTimeout(delay(1000).then(() => 'slow'), 500).catch(console.log); // Error: Timeout
/*
Solution:
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), ms);
});
return Promise.race([promise, timeout]);
}
// Tests:
withTimeout(delay(100).then(() => 'fast'), 500)
.then(console.log) // 'fast'
.catch(console.log);
withTimeout(delay(1000).then(() => 'slow'), 500)
.then(console.log)
.catch(e => console.log(e.message)); // 'Timeout'
*/
// --------------------------------------------
// Exercise 5: First Successful with Promise.any
// Try multiple servers and return first successful response
async function fetchFromServer(server) {
await delay(Math.random() * 200);
if (server === 'server1') throw new Error('Server 1 down');
return { server, data: 'content' };
}
// Your code here:
// async function fetchFromAnyServer(servers) {
// ...
// }
/*
Solution:
async function fetchFromAnyServer(servers) {
try {
return await Promise.any(servers.map(s => fetchFromServer(s)));
} catch (error) {
throw new Error('All servers failed');
}
}
fetchFromAnyServer(['server1', 'server2', 'server3']).then(console.log);
*/
// --------------------------------------------
// Exercise 6: Parallel with Concurrency Limit
// Execute promises in parallel but limit concurrent executions
// Your code here:
// async function parallelLimit(tasks, limit) {
// // tasks is array of functions that return promises
// // limit is max concurrent executions
// }
// Test:
// const tasks = [
// () => delay(100).then(() => 1),
// () => delay(100).then(() => 2),
// () => delay(100).then(() => 3),
// () => delay(100).then(() => 4),
// ];
// parallelLimit(tasks, 2).then(console.log); // [1, 2, 3, 4]
/*
Solution:
async function parallelLimit(tasks, limit) {
const results = [];
const executing = [];
for (const [index, task] of tasks.entries()) {
const promise = task().then(result => {
results[index] = result;
executing.splice(executing.indexOf(promise), 1);
});
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
await Promise.all(executing);
return results;
}
const tasks = [
() => delay(100).then(() => 1),
() => delay(100).then(() => 2),
() => delay(100).then(() => 3),
() => delay(100).then(() => 4),
];
parallelLimit(tasks, 2).then(console.log); // [1, 2, 3, 4]
*/
// --------------------------------------------
// Exercise 7: Promise.all with Progress
// Execute promises and report progress
// Your code here:
// async function allWithProgress(promises, onProgress) {
// // onProgress(completed, total) called after each completion
// }
/*
Solution:
async function allWithProgress(promises, onProgress) {
let completed = 0;
const total = promises.length;
const wrapped = promises.map(p =>
p.then(result => {
completed++;
onProgress(completed, total);
return result;
})
);
return Promise.all(wrapped);
}
const promises = [
delay(100).then(() => 'a'),
delay(200).then(() => 'b'),
delay(150).then(() => 'c')
];
allWithProgress(promises, (done, total) => {
console.log(`Progress: ${done}/${total}`);
}).then(console.log);
*/
// --------------------------------------------
// Exercise 8: Racing with Cleanup
// Race promises but cancel/cleanup losers
// Your code here:
// async function raceWithCleanup(entries) {
// // entries: [{ promise, cleanup: () => void }, ...]
// }
/*
Solution:
async function raceWithCleanup(entries) {
const promises = entries.map((entry, index) =>
entry.promise.then(value => ({ value, index }))
);
const winner = await Promise.race(promises);
// Cleanup losers
entries.forEach((entry, index) => {
if (index !== winner.index && entry.cleanup) {
entry.cleanup();
}
});
return winner.value;
}
// Usage example:
// raceWithCleanup([
// { promise: delay(100).then(() => 'A'), cleanup: () => console.log('Cleanup A') },
// { promise: delay(50).then(() => 'B'), cleanup: () => console.log('Cleanup B') }
// ]).then(console.log); // 'B', then 'Cleanup A'
*/
// --------------------------------------------
// Exercise 9: Retry with Promise.any
// Try operation multiple times in parallel
// Your code here:
// function retryParallel(fn, attempts) {
// // Run fn() 'attempts' times in parallel, return first success
// }
/*
Solution:
function retryParallel(fn, attempts) {
const promises = Array.from({ length: attempts }, (_, i) =>
fn(i).catch(error => {
throw new Error(`Attempt ${i + 1}: ${error.message}`);
})
);
return Promise.any(promises);
}
// Example:
let attemptCount = 0;
const unreliable = () => {
attemptCount++;
return delay(50).then(() => {
if (attemptCount < 3) throw new Error('Failed');
return 'Success!';
});
};
// retryParallel(unreliable, 5).then(console.log); // 'Success!'
*/
// --------------------------------------------
// Exercise 10: Batch Processing with allSettled
// Process items in batches, collect all results
// Your code here:
// async function processBatches(items, batchSize, processFn) {
// // Process in batches, return all results (success and failures)
// }
/*
Solution:
async function processBatches(items, batchSize, processFn) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.allSettled(
batch.map(item => processFn(item))
);
results.push(...batchResults);
}
return {
successful: results.filter(r => r.status === 'fulfilled').map(r => r.value),
failed: results.filter(r => r.status === 'rejected').map(r => r.reason)
};
}
const items = [1, 2, 3, 4, 5, 6, 7, 8];
const process = async (n) => {
await delay(50);
if (n % 3 === 0) throw new Error(`Failed on ${n}`);
return n * 2;
};
processBatches(items, 3, process).then(console.log);
*/
// --------------------------------------------
// Exercise 11: Sequential Fallback with any
// Try sources in order of preference, fall back to next
// Your code here:
// async function fetchWithFallbacks(sources) {
// // Try each source, use Promise.any but prefer earlier sources
// }
/*
Solution:
async function fetchWithFallbacks(sources) {
// Add delay to give preference to earlier sources
const promises = sources.map((source, index) =>
delay(index * 50).then(() => source.fetch())
);
return Promise.any(promises);
}
const sources = [
{ name: 'primary', fetch: async () => { throw new Error('Down'); } },
{ name: 'secondary', fetch: async () => ({ from: 'secondary', data: 'content' }) },
{ name: 'tertiary', fetch: async () => ({ from: 'tertiary', data: 'content' }) }
];
// fetchWithFallbacks(sources).then(console.log);
*/
// --------------------------------------------
// Exercise 12: Implement Promise.allSettled Polyfill
// Create your own version of Promise.allSettled
// Your code here:
// function myAllSettled(promises) {
// ...
// }
/*
Solution:
function myAllSettled(promises) {
return Promise.all(
promises.map(p =>
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
)
);
}
myAllSettled([
Promise.resolve(1),
Promise.reject(new Error('fail')),
Promise.resolve(3)
]).then(console.log);
*/
// --------------------------------------------
// Exercise 13: Implement Promise.any Polyfill
// Create your own version of Promise.any
// Your code here:
// function myAny(promises) {
// ...
// }
/*
Solution:
function myAny(promises) {
return new Promise((resolve, reject) => {
const errors = [];
let pending = promises.length;
if (pending === 0) {
reject(new AggregateError([], 'All promises were rejected'));
return;
}
promises.forEach((p, index) => {
Promise.resolve(p)
.then(resolve)
.catch(error => {
errors[index] = error;
pending--;
if (pending === 0) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
}
myAny([
Promise.reject(1),
delay(100).then(() => 'success'),
Promise.reject(3)
]).then(console.log); // 'success'
*/
// --------------------------------------------
// Exercise 14: Load Balancer
// Distribute requests across servers based on response time
// Your code here:
// class LoadBalancer {
// constructor(servers) { ... }
// async request(data) { ... }
// }
/*
Solution:
class LoadBalancer {
constructor(servers) {
this.servers = servers.map(s => ({
url: s,
avgLatency: 0,
requests: 0
}));
}
async request(data) {
// Race all servers, track latency
const start = Date.now();
const result = await Promise.any(
this.servers.map(async server => {
const response = await this.fetchFromServer(server.url, data);
const latency = Date.now() - start;
// Update stats
server.avgLatency = (server.avgLatency * server.requests + latency) / (server.requests + 1);
server.requests++;
return { response, server: server.url, latency };
})
);
return result;
}
async fetchFromServer(url, data) {
await delay(Math.random() * 200);
return { url, data: 'response' };
}
getStats() {
return this.servers.map(s => ({
url: s.url,
avgLatency: s.avgLatency.toFixed(2),
requests: s.requests
}));
}
}
// const lb = new LoadBalancer(['server1', 'server2', 'server3']);
// lb.request({}).then(console.log);
*/
// --------------------------------------------
// Exercise 15: Coordinated Async Operations
// Wait for setup tasks before running main tasks
// Your code here:
// async function coordinatedExecution(setupTasks, mainTasks) {
// // Run all setup tasks first (all must succeed)
// // Then run main tasks in parallel
// // Return { setupResults, mainResults }
// }
/*
Solution:
async function coordinatedExecution(setupTasks, mainTasks) {
// Setup phase - all must succeed
const setupResults = await Promise.all(
setupTasks.map(task => task())
);
console.log('Setup complete, running main tasks...');
// Main phase - collect all results
const mainResults = await Promise.allSettled(
mainTasks.map(task => task())
);
return {
setupResults,
mainResults: {
successful: mainResults.filter(r => r.status === 'fulfilled').map(r => r.value),
failed: mainResults.filter(r => r.status === 'rejected').map(r => r.reason.message)
}
};
}
const setupTasks = [
() => delay(50).then(() => 'DB Connected'),
() => delay(30).then(() => 'Cache Ready')
];
const mainTasks = [
() => delay(100).then(() => 'Task 1 done'),
() => delay(50).then(() => { throw new Error('Task 2 failed'); }),
() => delay(80).then(() => 'Task 3 done')
];
coordinatedExecution(setupTasks, mainTasks).then(console.log);
*/