javascript

exercises

exercises.js
/**
 * ========================================
 * 9.2 PROMISES - EXERCISES
 * ========================================
 *
 * Practice working with JavaScript Promises.
 *
 * Instructions:
 * 1. Read each exercise description carefully
 * 2. Implement the solution below the exercise
 * 3. Check the solution in the comments if stuck
 */

/**
 * EXERCISE 1: Create a Promise
 *
 * Create a function `wait` that returns a promise which:
 * - Resolves after the specified milliseconds
 * - Resolves with the message "Done waiting!"
 */
console.log('--- Exercise 1: Create a Promise ---');

// Your code here:

// Test case:
// wait(1000).then(msg => console.log(msg)); // After 1s: "Done waiting!"

/*
 * SOLUTION 1:
 *
 * function wait(ms) {
 *     return new Promise((resolve) => {
 *         setTimeout(() => {
 *             resolve("Done waiting!");
 *         }, ms);
 *     });
 * }
 */

/**
 * EXERCISE 2: Promise with Rejection
 *
 * Create a function `divide` that:
 * - Takes two numbers
 * - Returns a promise
 * - Resolves with the result if valid
 * - Rejects with an error if dividing by zero
 */
console.log('\n--- Exercise 2: Promise with Rejection ---');

// Your code here:

// Test cases:
// divide(10, 2).then(r => console.log(r)); // 5
// divide(10, 0).catch(e => console.log(e.message)); // "Cannot divide by zero"

/*
 * SOLUTION 2:
 *
 * function divide(a, b) {
 *     return new Promise((resolve, reject) => {
 *         if (b === 0) {
 *             reject(new Error("Cannot divide by zero"));
 *         } else {
 *             resolve(a / b);
 *         }
 *     });
 * }
 */

/**
 * EXERCISE 3: Promise Chaining
 *
 * Create a function `processNumber` that:
 * - Takes a number
 * - Returns a promise chain that:
 *   1. Doubles the number
 *   2. Adds 10
 *   3. Converts to string with " points" suffix
 */
console.log('\n--- Exercise 3: Promise Chaining ---');

// Your code here:

// Test case:
// processNumber(5).then(r => console.log(r)); // "20 points"

/*
 * SOLUTION 3:
 *
 * function processNumber(num) {
 *     return Promise.resolve(num)
 *         .then(n => n * 2)
 *         .then(n => n + 10)
 *         .then(n => `${n} points`);
 * }
 */

/**
 * EXERCISE 4: Error Handling
 *
 * Create a function `fetchUser` that:
 * - Takes a user ID
 * - Returns a promise
 * - Resolves with user object if ID > 0
 * - Rejects with "Invalid ID" if ID <= 0
 * - Rejects with "User not found" if ID > 100
 */
console.log('\n--- Exercise 4: Error Handling ---');

// Your code here:

// Test cases:
// fetchUser(1).then(u => console.log(u)); // { id: 1, name: "User 1" }
// fetchUser(0).catch(e => console.log(e.message)); // "Invalid ID"
// fetchUser(101).catch(e => console.log(e.message)); // "User not found"

/*
 * SOLUTION 4:
 *
 * function fetchUser(id) {
 *     return new Promise((resolve, reject) => {
 *         setTimeout(() => {
 *             if (id <= 0) {
 *                 reject(new Error("Invalid ID"));
 *             } else if (id > 100) {
 *                 reject(new Error("User not found"));
 *             } else {
 *                 resolve({ id, name: `User ${id}` });
 *             }
 *         }, 100);
 *     });
 * }
 */

/**
 * EXERCISE 5: Promise.all
 *
 * Create a function `fetchAllUsers` that:
 * - Takes an array of user IDs
 * - Fetches all users in parallel using Promise.all
 * - Returns array of user objects
 */
console.log('\n--- Exercise 5: Promise.all ---');

// Your code here:

// Test case:
// fetchAllUsers([1, 2, 3]).then(users => console.log(users));
// // [{ id: 1, name: "User 1" }, { id: 2, name: "User 2" }, { id: 3, name: "User 3" }]

/*
 * SOLUTION 5:
 *
 * function fetchAllUsers(ids) {
 *     const promises = ids.map(id => {
 *         return new Promise((resolve) => {
 *             setTimeout(() => {
 *                 resolve({ id, name: `User ${id}` });
 *             }, 50);
 *         });
 *     });
 *     return Promise.all(promises);
 * }
 */

/**
 * EXERCISE 6: Promise.allSettled
 *
 * Create a function `fetchUsersSettled` that:
 * - Fetches multiple users (some may fail)
 * - Returns all results with status
 * - Even IDs succeed, odd IDs fail
 */
console.log('\n--- Exercise 6: Promise.allSettled ---');

// Your code here:

// Test case:
// fetchUsersSettled([1, 2, 3, 4]).then(results => {
//     results.forEach((r, i) => {
//         if (r.status === 'fulfilled') console.log(`User ${i+1}: ${r.value.name}`);
//         else console.log(`User ${i+1}: ${r.reason.message}`);
//     });
// });

/*
 * SOLUTION 6:
 *
 * function fetchUsersSettled(ids) {
 *     const promises = ids.map(id => {
 *         return new Promise((resolve, reject) => {
 *             setTimeout(() => {
 *                 if (id % 2 === 0) {
 *                     resolve({ id, name: `User ${id}` });
 *                 } else {
 *                     reject(new Error(`Failed to fetch user ${id}`));
 *                 }
 *             }, 50);
 *         });
 *     });
 *     return Promise.allSettled(promises);
 * }
 */

/**
 * EXERCISE 7: Promise.race
 *
 * Create a function `fetchWithTimeout` that:
 * - Takes a fetch function and timeout in ms
 * - Returns the fetch result if it completes in time
 * - Rejects with "Timeout" if too slow
 */
console.log('\n--- Exercise 7: Promise.race ---');

// Your code here:

// Test case:
// const slowFetch = () => new Promise(r => setTimeout(() => r("data"), 500));
// const fastFetch = () => new Promise(r => setTimeout(() => r("data"), 50));
//
// fetchWithTimeout(slowFetch, 200).catch(e => console.log(e.message)); // "Timeout"
// fetchWithTimeout(fastFetch, 200).then(r => console.log(r)); // "data"

/*
 * SOLUTION 7:
 *
 * function fetchWithTimeout(fetchFn, timeout) {
 *     const timeoutPromise = new Promise((_, reject) => {
 *         setTimeout(() => reject(new Error("Timeout")), timeout);
 *     });
 *     return Promise.race([fetchFn(), timeoutPromise]);
 * }
 */

/**
 * EXERCISE 8: Promise.any
 *
 * Create a function `fetchFromMirrors` that:
 * - Takes an array of mirror URLs
 * - Returns the first successful response
 * - Each mirror might fail randomly
 */
console.log('\n--- Exercise 8: Promise.any ---');

// Your code here:

// Test case:
// const mirrors = ["mirror1.com", "mirror2.com", "mirror3.com"];
// fetchFromMirrors(mirrors).then(r => console.log("Got from:", r));

/*
 * SOLUTION 8:
 *
 * function fetchFromMirrors(mirrors) {
 *     const promises = mirrors.map(mirror => {
 *         return new Promise((resolve, reject) => {
 *             setTimeout(() => {
 *                 if (Math.random() > 0.5) {
 *                     resolve(mirror);
 *                 } else {
 *                     reject(new Error(`${mirror} failed`));
 *                 }
 *             }, Math.random() * 200);
 *         });
 *     });
 *     return Promise.any(promises);
 * }
 */

/**
 * EXERCISE 9: Retry Logic
 *
 * Create a function `retry` that:
 * - Takes a promise-returning function and max attempts
 * - Retries on failure up to max attempts
 * - Returns last error if all attempts fail
 */
console.log('\n--- Exercise 9: Retry Logic ---');

// Your code here:

// Test case:
// let attempts = 0;
// const flaky = () => new Promise((res, rej) => {
//     attempts++;
//     attempts < 3 ? rej(new Error("fail")) : res("success");
// });
//
// retry(flaky, 5).then(r => console.log(r)); // "success" on 3rd attempt

/*
 * SOLUTION 9:
 *
 * function retry(fn, attempts) {
 *     return fn().catch(error => {
 *         if (attempts <= 1) {
 *             throw error;
 *         }
 *         return retry(fn, attempts - 1);
 *     });
 * }
 */

/**
 * EXERCISE 10: Sequential Execution
 *
 * Create a function `sequential` that:
 * - Takes an array of promise-returning functions
 * - Executes them one after another
 * - Returns array of all results
 */
console.log('\n--- Exercise 10: Sequential Execution ---');

// Your code here:

// Test case:
// const tasks = [
//     () => Promise.resolve(1),
//     () => Promise.resolve(2),
//     () => Promise.resolve(3)
// ];
// sequential(tasks).then(r => console.log(r)); // [1, 2, 3]

/*
 * SOLUTION 10:
 *
 * function sequential(tasks) {
 *     return tasks.reduce((promise, task) => {
 *         return promise.then(results => {
 *             return task().then(result => [...results, result]);
 *         });
 *     }, Promise.resolve([]));
 * }
 */

/**
 * EXERCISE 11: Promisify
 *
 * Create a function `promisify` that:
 * - Takes a callback-style function
 * - Returns a promise-returning version
 * - Assumes error-first callback pattern
 */
console.log('\n--- Exercise 11: Promisify ---');

// Your code here:

// Test case:
// function callbackFn(x, callback) {
//     setTimeout(() => callback(null, x * 2), 50);
// }
// const promiseFn = promisify(callbackFn);
// promiseFn(5).then(r => console.log(r)); // 10

/*
 * SOLUTION 11:
 *
 * function promisify(fn) {
 *     return function(...args) {
 *         return new Promise((resolve, reject) => {
 *             fn(...args, (error, result) => {
 *                 if (error) reject(error);
 *                 else resolve(result);
 *             });
 *         });
 *     };
 * }
 */

/**
 * EXERCISE 12: Deferred Pattern
 *
 * Create a `Deferred` class that:
 * - Has a promise property
 * - Has resolve() and reject() methods
 * - Can be resolved/rejected externally
 */
console.log('\n--- Exercise 12: Deferred Pattern ---');

// Your code here:

// Test case:
// const deferred = new Deferred();
// setTimeout(() => deferred.resolve("Done!"), 100);
// deferred.promise.then(r => console.log(r)); // "Done!"

/*
 * SOLUTION 12:
 *
 * class Deferred {
 *     constructor() {
 *         this.promise = new Promise((resolve, reject) => {
 *             this.resolve = resolve;
 *             this.reject = reject;
 *         });
 *     }
 * }
 */

/**
 * EXERCISE 13: Throttle Promises
 *
 * Create a function `throttle` that:
 * - Takes an array of promise factories
 * - Executes at most N at a time
 * - Returns results in original order
 */
console.log('\n--- Exercise 13: Throttle Promises ---');

// Your code here:

// Test case:
// const tasks = [
//     () => new Promise(r => setTimeout(() => r(1), 100)),
//     () => new Promise(r => setTimeout(() => r(2), 50)),
//     () => new Promise(r => setTimeout(() => r(3), 75)),
//     () => new Promise(r => setTimeout(() => r(4), 25))
// ];
// throttle(tasks, 2).then(r => console.log(r)); // [1, 2, 3, 4]

/*
 * SOLUTION 13:
 *
 * function throttle(factories, limit) {
 *     const results = [];
 *     let index = 0;
 *
 *     return new Promise((resolve) => {
 *         function next() {
 *             if (index >= factories.length) {
 *                 if (results.filter(() => true).length === factories.length) {
 *                     resolve(results);
 *                 }
 *                 return;
 *             }
 *
 *             const currentIndex = index++;
 *             factories[currentIndex]().then(result => {
 *                 results[currentIndex] = result;
 *                 next();
 *             });
 *         }
 *
 *         for (let i = 0; i < Math.min(limit, factories.length); i++) {
 *             next();
 *         }
 *     });
 * }
 */

/**
 * EXERCISE 14: Promise Memoization
 *
 * Create a function `memoize` that:
 * - Takes an async function
 * - Caches results by arguments
 * - Returns cached promise for same arguments
 */
console.log('\n--- Exercise 14: Promise Memoization ---');

// Your code here:

// Test case:
// const slowFetch = memoize((id) => {
//     console.log("Fetching:", id);
//     return new Promise(r => setTimeout(() => r({ id }), 100));
// });
//
// slowFetch(1).then(console.log); // Logs "Fetching: 1", then { id: 1 }
// slowFetch(1).then(console.log); // Just { id: 1 } (cached)
// slowFetch(2).then(console.log); // Logs "Fetching: 2", then { id: 2 }

/*
 * SOLUTION 14:
 *
 * function memoize(fn) {
 *     const cache = new Map();
 *
 *     return function(...args) {
 *         const key = JSON.stringify(args);
 *
 *         if (cache.has(key)) {
 *             return cache.get(key);
 *         }
 *
 *         const promise = fn(...args);
 *         cache.set(key, promise);
 *         return promise;
 *     };
 * }
 */

/**
 * EXERCISE 15: Promise-based Event
 *
 * Create a function `waitForEvent` that:
 * - Takes an event emitter and event name
 * - Returns a promise that resolves when event fires
 * - Resolves with the event data
 */
console.log('\n--- Exercise 15: Promise-based Event ---');

// Your code here:

// Test case:
// class EventEmitter {
//     constructor() { this.handlers = {}; }
//     on(event, handler) {
//         if (!this.handlers[event]) this.handlers[event] = [];
//         this.handlers[event].push(handler);
//     }
//     emit(event, data) {
//         (this.handlers[event] || []).forEach(h => h(data));
//     }
// }
//
// const emitter = new EventEmitter();
// const promise = waitForEvent(emitter, 'data');
// setTimeout(() => emitter.emit('data', { value: 42 }), 100);
// promise.then(data => console.log(data)); // { value: 42 }

/*
 * SOLUTION 15:
 *
 * function waitForEvent(emitter, eventName) {
 *     return new Promise((resolve) => {
 *         const handler = (data) => {
 *             resolve(data);
 *         };
 *         emitter.on(eventName, handler);
 *     });
 * }
 */

console.log('\n========================================');
console.log('End of Promise Exercises');
console.log('========================================');
Exercises - JavaScript Tutorial | DeepML