javascript

exercises

exercises.js
/**
 * ============================================================
 * 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.'
);
Exercises - JavaScript Tutorial | DeepML