javascript
exercises
exercises.js⚡javascript
/**
* ========================================
* 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('========================================');