javascript
exercises
exercises.js⚡javascript
/**
* ============================================================
* 9.3 ASYNC/AWAIT - EXERCISES
* ============================================================
*
* Complete each exercise by implementing the async functions.
* Test your solutions by running this file.
*/
// Helper: Simulates async operation
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
// Helper: Simulates fetch failure
function failAfter(ms, message) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error(message)), ms)
);
}
/**
* EXERCISE 1: Basic Async Function
*
* Create an async function 'getMessage' that:
* - Waits 100ms
* - Returns "Hello, Async World!"
*/
// TODO: Implement getMessage
async function getMessage() {
// Your code here
}
// Test
// getMessage().then(msg => console.log('Exercise 1:', msg));
/*
* SOLUTION 1:
*
* async function getMessage() {
* await delay(100);
* return "Hello, Async World!";
* }
*/
/**
* EXERCISE 2: Awaiting Multiple Values
*
* Create an async function 'getProfile' that:
* - Fetches user from delay(50, { name: 'John' })
* - Fetches settings from delay(50, { theme: 'dark' })
* - Returns combined object { user, settings }
* - Operations should run in PARALLEL
*/
// TODO: Implement getProfile
async function getProfile() {
// Your code here
}
// Test
// getProfile().then(profile => console.log('Exercise 2:', profile));
/*
* SOLUTION 2:
*
* async function getProfile() {
* const [user, settings] = await Promise.all([
* delay(50, { name: 'John' }),
* delay(50, { theme: 'dark' })
* ]);
* return { user, settings };
* }
*/
/**
* EXERCISE 3: Error Handling
*
* Create an async function 'safeFetch' that:
* - Tries to await a promise that rejects
* - Catches the error
* - Returns { success: false, error: errorMessage }
* - If promise resolves, returns { success: true, data: value }
*/
// TODO: Implement safeFetch
async function safeFetch(promise) {
// Your code here
}
// Test
// safeFetch(delay(50, 'data')).then(r => console.log('Exercise 3 success:', r));
// safeFetch(failAfter(50, 'Failed!')).then(r => console.log('Exercise 3 error:', r));
/*
* SOLUTION 3:
*
* async function safeFetch(promise) {
* try {
* const data = await promise;
* return { success: true, data };
* } catch (error) {
* return { success: false, error: error.message };
* }
* }
*/
/**
* EXERCISE 4: Sequential Processing
*
* Create an async function 'processSequence' that:
* - Takes an array of numbers
* - For each number, waits (number * 10)ms
* - Returns array of results: [{ value, waited }]
* - Processing must be SEQUENTIAL
*/
// TODO: Implement processSequence
async function processSequence(numbers) {
// Your code here
}
// Test
// processSequence([1, 2, 3]).then(r => console.log('Exercise 4:', r));
/*
* SOLUTION 4:
*
* async function processSequence(numbers) {
* const results = [];
* for (const value of numbers) {
* const waited = value * 10;
* await delay(waited);
* results.push({ value, waited });
* }
* return results;
* }
*/
/**
* EXERCISE 5: Retry Logic
*
* Create an async function 'retryAsync' that:
* - Takes (fn, maxRetries)
* - Calls fn(), which returns a promise
* - If it fails, retries up to maxRetries times
* - Waits 50ms between retries
* - Returns result on success or throws after all retries
*/
// TODO: Implement retryAsync
async function retryAsync(fn, maxRetries) {
// Your code here
}
// Test
// let count = 0;
// const flaky = () => {
// count++;
// return count < 3 ? Promise.reject(new Error('fail')) : Promise.resolve('success');
// };
// retryAsync(flaky, 5).then(r => console.log('Exercise 5:', r));
/*
* SOLUTION 5:
*
* async function retryAsync(fn, maxRetries) {
* for (let i = 0; i < maxRetries; i++) {
* try {
* return await fn();
* } catch (error) {
* if (i === maxRetries - 1) throw error;
* await delay(50);
* }
* }
* }
*/
/**
* EXERCISE 6: Timeout Wrapper
*
* Create an async function 'withTimeout' that:
* - Takes (promise, timeoutMs)
* - Returns promise result if it resolves within timeoutMs
* - Throws Error('Timeout') if time expires
*/
// TODO: Implement withTimeout
async function withTimeout(promise, timeoutMs) {
// Your code here
}
// Test
// withTimeout(delay(50, 'fast'), 100).then(r => console.log('Exercise 6 fast:', r));
// withTimeout(delay(200, 'slow'), 100).catch(e => console.log('Exercise 6 slow:', e.message));
/*
* SOLUTION 6:
*
* async function withTimeout(promise, timeoutMs) {
* const timeout = new Promise((_, reject) => {
* setTimeout(() => reject(new Error('Timeout')), timeoutMs);
* });
* return Promise.race([promise, timeout]);
* }
*/
/**
* EXERCISE 7: Async Filter
*
* Create an async function 'asyncFilter' that:
* - Takes (array, asyncPredicate)
* - asyncPredicate is an async function returning boolean
* - Returns filtered array where predicate returned true
* - Run predicates in PARALLEL
*/
// TODO: Implement asyncFilter
async function asyncFilter(array, asyncPredicate) {
// Your code here
}
// Test
// const isEven = async n => { await delay(50); return n % 2 === 0; };
// asyncFilter([1, 2, 3, 4, 5], isEven).then(r => console.log('Exercise 7:', r));
/*
* SOLUTION 7:
*
* async function asyncFilter(array, asyncPredicate) {
* const results = await Promise.all(
* array.map(async item => ({
* item,
* keep: await asyncPredicate(item)
* }))
* );
* return results.filter(r => r.keep).map(r => r.item);
* }
*/
/**
* EXERCISE 8: Async Map with Concurrency Limit
*
* Create an async function 'asyncMapLimited' that:
* - Takes (array, asyncFn, limit)
* - Applies asyncFn to each item
* - Runs at most 'limit' operations concurrently
* - Returns results in original order
*/
// TODO: Implement asyncMapLimited
async function asyncMapLimited(array, asyncFn, limit) {
// Your code here
}
// Test
// const double = async n => { await delay(50); return n * 2; };
// asyncMapLimited([1, 2, 3, 4, 5], double, 2).then(r => console.log('Exercise 8:', r));
/*
* SOLUTION 8:
*
* async function asyncMapLimited(array, asyncFn, limit) {
* const results = new Array(array.length);
* let index = 0;
*
* async function worker() {
* while (index < array.length) {
* const i = index++;
* results[i] = await asyncFn(array[i]);
* }
* }
*
* const workers = Array(Math.min(limit, array.length))
* .fill(null)
* .map(() => worker());
*
* await Promise.all(workers);
* return results;
* }
*/
/**
* EXERCISE 9: Polling Until Condition
*
* Create an async function 'pollUntil' that:
* - Takes (asyncFn, conditionFn, intervalMs, maxAttempts)
* - Calls asyncFn repeatedly until conditionFn(result) returns true
* - Waits intervalMs between attempts
* - Throws after maxAttempts if condition never met
* - Returns the result when condition is met
*/
// TODO: Implement pollUntil
async function pollUntil(asyncFn, conditionFn, intervalMs, maxAttempts) {
// Your code here
}
// Test
// let pollNum = 0;
// const getStatus = async () => ({ ready: ++pollNum >= 3, count: pollNum });
// pollUntil(getStatus, r => r.ready, 50, 5).then(r => console.log('Exercise 9:', r));
/*
* SOLUTION 9:
*
* async function pollUntil(asyncFn, conditionFn, intervalMs, maxAttempts) {
* for (let i = 0; i < maxAttempts; i++) {
* const result = await asyncFn();
* if (conditionFn(result)) {
* return result;
* }
* if (i < maxAttempts - 1) {
* await delay(intervalMs);
* }
* }
* throw new Error('Polling timeout');
* }
*/
/**
* EXERCISE 10: Async Queue
*
* Create a class 'AsyncQueue' that:
* - Has method 'add(asyncTask)' that returns a promise
* - Runs tasks one at a time in order added
* - Each task is a function that returns a promise
* - Returns task result when complete
*/
// TODO: Implement AsyncQueue
class AsyncQueue {
// Your code here
}
// Test
// const queue = new AsyncQueue();
// queue.add(() => delay(100, 'first')).then(r => console.log('Exercise 10:', r));
// queue.add(() => delay(50, 'second')).then(r => console.log('Exercise 10:', r));
// queue.add(() => delay(25, 'third')).then(r => console.log('Exercise 10:', r));
/*
* SOLUTION 10:
*
* class AsyncQueue {
* #queue = [];
* #running = false;
*
* add(asyncTask) {
* return new Promise((resolve, reject) => {
* this.#queue.push({ task: asyncTask, resolve, reject });
* this.#process();
* });
* }
*
* async #process() {
* if (this.#running) return;
* this.#running = true;
*
* while (this.#queue.length > 0) {
* const { task, resolve, reject } = this.#queue.shift();
* try {
* const result = await task();
* resolve(result);
* } catch (error) {
* reject(error);
* }
* }
*
* this.#running = false;
* }
* }
*/
/**
* EXERCISE 11: Async Reduce
*
* Create an async function 'asyncReduce' that:
* - Takes (array, asyncReducer, initialValue)
* - asyncReducer is async (accumulator, item) => newAccumulator
* - Returns final accumulated value
* - Must process sequentially
*/
// TODO: Implement asyncReduce
async function asyncReduce(array, asyncReducer, initialValue) {
// Your code here
}
// Test
// const sumAsync = async (acc, n) => { await delay(50); return acc + n; };
// asyncReduce([1, 2, 3, 4], sumAsync, 0).then(r => console.log('Exercise 11:', r));
/*
* SOLUTION 11:
*
* async function asyncReduce(array, asyncReducer, initialValue) {
* let accumulator = initialValue;
* for (const item of array) {
* accumulator = await asyncReducer(accumulator, item);
* }
* return accumulator;
* }
*/
/**
* EXERCISE 12: First Successful Result
*
* Create an async function 'firstSuccess' that:
* - Takes array of promises
* - Returns first successfully resolved value
* - Ignores rejections unless ALL promises reject
* - If all reject, throws AggregateError with all errors
*/
// TODO: Implement firstSuccess
async function firstSuccess(promises) {
// Your code here
}
// Test
// firstSuccess([
// failAfter(50, 'Error 1'),
// delay(100, 'Success!'),
// failAfter(75, 'Error 2')
// ]).then(r => console.log('Exercise 12:', r));
/*
* SOLUTION 12:
*
* async function firstSuccess(promises) {
* return Promise.any(promises);
* // OR manual implementation:
* // const errors = [];
* // for (const promise of promises) {
* // try {
* // return await promise;
* // } catch (error) {
* // errors.push(error);
* // }
* // }
* // throw new AggregateError(errors, 'All promises rejected');
* }
*/
/**
* EXERCISE 13: Batch Processor
*
* Create an async function 'processBatches' that:
* - Takes (items, batchSize, asyncProcessor)
* - Processes items in batches of batchSize
* - Each batch processes in parallel
* - Batches run sequentially
* - Returns all results in order
*/
// TODO: Implement processBatches
async function processBatches(items, batchSize, asyncProcessor) {
// Your code here
}
// Test
// const process = async n => { await delay(50); return n * 2; };
// processBatches([1, 2, 3, 4, 5, 6, 7], 3, process).then(r => console.log('Exercise 13:', r));
/*
* SOLUTION 13:
*
* async function processBatches(items, batchSize, asyncProcessor) {
* const results = [];
* for (let i = 0; i < items.length; i += batchSize) {
* const batch = items.slice(i, i + batchSize);
* const batchResults = await Promise.all(batch.map(asyncProcessor));
* results.push(...batchResults);
* }
* return results;
* }
*/
/**
* EXERCISE 14: Async Cache
*
* Create a class 'AsyncCache' that:
* - Has method 'get(key, asyncFetcher)'
* - Returns cached value if exists
* - Calls asyncFetcher(key) if not cached
* - Prevents duplicate fetches for same key (deduplication)
* - Caches result for future calls
*/
// TODO: Implement AsyncCache
class AsyncCache {
// Your code here
}
// Test
// const cache = new AsyncCache();
// const fetch = async key => { console.log('Fetching:', key); await delay(100); return `Value for ${key}`; };
// Promise.all([
// cache.get('a', fetch),
// cache.get('a', fetch), // Should not fetch again
// cache.get('b', fetch)
// ]).then(results => console.log('Exercise 14:', results));
/*
* SOLUTION 14:
*
* class AsyncCache {
* #cache = new Map();
* #pending = new Map();
*
* async get(key, asyncFetcher) {
* if (this.#cache.has(key)) {
* return this.#cache.get(key);
* }
*
* if (this.#pending.has(key)) {
* return this.#pending.get(key);
* }
*
* const promise = asyncFetcher(key);
* this.#pending.set(key, promise);
*
* try {
* const value = await promise;
* this.#cache.set(key, value);
* return value;
* } finally {
* this.#pending.delete(key);
* }
* }
* }
*/
/**
* EXERCISE 15: Async Resource Manager
*
* Create a class 'ResourceManager' that:
* - Has async 'acquire()' that returns a resource
* - Has async 'release(resource)' that releases it
* - Has async 'use(asyncFn)' that:
* - Acquires resource
* - Passes to asyncFn
* - Always releases even if asyncFn throws
* - Returns asyncFn result
*/
// TODO: Implement ResourceManager
class ResourceManager {
constructor() {
this.resourceId = 0;
}
// Your code here
}
// Test
// const manager = new ResourceManager();
// manager.use(async resource => {
// console.log('Exercise 15: Using', resource);
// await delay(100);
// return `Result from ${resource.id}`;
// }).then(r => console.log('Exercise 15:', r));
/*
* SOLUTION 15:
*
* class ResourceManager {
* constructor() {
* this.resourceId = 0;
* }
*
* async acquire() {
* await delay(50); // Simulate connection time
* return { id: ++this.resourceId, acquired: true };
* }
*
* async release(resource) {
* await delay(10); // Simulate cleanup
* resource.released = true;
* console.log(`Resource ${resource.id} released`);
* }
*
* async use(asyncFn) {
* const resource = await this.acquire();
* try {
* return await asyncFn(resource);
* } finally {
* await this.release(resource);
* }
* }
* }
*/
// ============================================================
// RUN TESTS (Uncomment to test your solutions)
// ============================================================
async function runAllTests() {
console.log('Testing Exercise 1...');
// Add your test code here
console.log('\nAll tests completed!');
}
// runAllTests();
console.log(
'Async/Await Exercises loaded. Uncomment tests to verify solutions.'
);