javascript

exercises

exercises.js
/**
 * ============================================================
 * 9.5 TIMERS AND INTERVALS - EXERCISES
 * ============================================================
 *
 * Complete each exercise by implementing the timer functions.
 * Test your solutions by running this file.
 */

// Helper
function delay(ms, value) {
  return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}

/**
 * EXERCISE 1: Basic Delay Promise
 *
 * Create a function 'wait' that:
 * - Takes a number of milliseconds
 * - Returns a promise that resolves after that time
 * - Resolves with the message "Done waiting"
 */

// TODO: Implement wait
function wait(ms) {
  // Your code here
}

// Test
// wait(100).then(msg => console.log('Exercise 1:', msg));

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

/**
 * EXERCISE 2: Timeout with Value
 *
 * Create a function 'delayedValue' that:
 * - Takes (ms, value)
 * - Returns a promise that resolves with value after ms
 */

// TODO: Implement delayedValue
function delayedValue(ms, value) {
  // Your code here
}

// Test
// delayedValue(100, { name: 'test' }).then(v => console.log('Exercise 2:', v));

/*
 * SOLUTION 2:
 *
 * function delayedValue(ms, value) {
 *     return new Promise(resolve => setTimeout(() => resolve(value), ms));
 * }
 */

/**
 * EXERCISE 3: Timeout Reject
 *
 * Create a function 'rejectAfter' that:
 * - Takes (ms, message)
 * - Returns a promise that rejects with Error(message) after ms
 */

// TODO: Implement rejectAfter
function rejectAfter(ms, message) {
  // Your code here
}

// Test
// rejectAfter(100, 'Timeout!').catch(e => console.log('Exercise 3:', e.message));

/*
 * SOLUTION 3:
 *
 * function rejectAfter(ms, message) {
 *     return new Promise((_, reject) => {
 *         setTimeout(() => reject(new Error(message)), ms);
 *     });
 * }
 */

/**
 * EXERCISE 4: Cancellable Delay
 *
 * Create a function 'cancellableDelay' that:
 * - Takes (ms, signal) where signal is an AbortSignal
 * - Returns a promise that resolves after ms
 * - Rejects immediately if signal is aborted
 * - Cleans up the timeout when aborted
 */

// TODO: Implement cancellableDelay
function cancellableDelay(ms, signal) {
  // Your code here
}

// Test
// const controller = new AbortController();
// cancellableDelay(500, controller.signal)
//     .then(() => console.log('Exercise 4: Completed'))
//     .catch(e => console.log('Exercise 4:', e.message));
// setTimeout(() => controller.abort(), 100);

/*
 * SOLUTION 4:
 *
 * function cancellableDelay(ms, signal) {
 *     return new Promise((resolve, reject) => {
 *         const timeoutId = setTimeout(resolve, ms);
 *
 *         signal?.addEventListener('abort', () => {
 *             clearTimeout(timeoutId);
 *             reject(new Error('Aborted'));
 *         });
 *     });
 * }
 */

/**
 * EXERCISE 5: Simple Debounce
 *
 * Create a function 'debounce' that:
 * - Takes (fn, wait)
 * - Returns a debounced function
 * - Only calls fn after wait ms of no calls
 */

// TODO: Implement debounce
function debounce(fn, wait) {
  // Your code here
}

// Test
// const debouncedLog = debounce(x => console.log('Exercise 5:', x), 100);
// debouncedLog(1); debouncedLog(2); debouncedLog(3);
// // Should only log "3" after 100ms

/*
 * SOLUTION 5:
 *
 * function debounce(fn, wait) {
 *     let timeoutId;
 *     return function(...args) {
 *         clearTimeout(timeoutId);
 *         timeoutId = setTimeout(() => fn.apply(this, args), wait);
 *     };
 * }
 */

/**
 * EXERCISE 6: Debounce with Immediate
 *
 * Create a function 'debounceImmediate' that:
 * - Takes (fn, wait, immediate = false)
 * - If immediate is true, calls fn on first call
 * - Still waits for quiet period before next call
 */

// TODO: Implement debounceImmediate
function debounceImmediate(fn, wait, immediate = false) {
  // Your code here
}

// Test
// const immediateDeb = debounceImmediate(x => console.log('Exercise 6:', x), 100, true);
// immediateDeb('first');  // Logs immediately
// immediateDeb('second'); // Ignored
// immediateDeb('third');  // Ignored
// // After 100ms of quiet, next call will log immediately again

/*
 * SOLUTION 6:
 *
 * function debounceImmediate(fn, wait, immediate = false) {
 *     let timeoutId;
 *     let canCall = true;
 *
 *     return function(...args) {
 *         if (immediate && canCall) {
 *             fn.apply(this, args);
 *             canCall = false;
 *         }
 *
 *         clearTimeout(timeoutId);
 *         timeoutId = setTimeout(() => {
 *             if (!immediate) fn.apply(this, args);
 *             canCall = true;
 *         }, wait);
 *     };
 * }
 */

/**
 * EXERCISE 7: Simple Throttle
 *
 * Create a function 'throttle' that:
 * - Takes (fn, limit)
 * - Returns a throttled function
 * - Only allows fn to be called once per limit ms
 */

// TODO: Implement throttle
function throttle(fn, limit) {
  // Your code here
}

// Test
// const throttledLog = throttle(x => console.log('Exercise 7:', x), 100);
// throttledLog(1); // Logs
// throttledLog(2); // Ignored
// setTimeout(() => throttledLog(3), 150); // Logs

/*
 * SOLUTION 7:
 *
 * function throttle(fn, limit) {
 *     let inThrottle = false;
 *     return function(...args) {
 *         if (!inThrottle) {
 *             fn.apply(this, args);
 *             inThrottle = true;
 *             setTimeout(() => inThrottle = false, limit);
 *         }
 *     };
 * }
 */

/**
 * EXERCISE 8: Throttle with Trailing
 *
 * Create a function 'throttleTrailing' that:
 * - Takes (fn, limit)
 * - Calls fn immediately on first call
 * - Also calls fn with last arguments after limit if calls were made
 */

// TODO: Implement throttleTrailing
function throttleTrailing(fn, limit) {
  // Your code here
}

// Test
// const trailingThrottle = throttleTrailing(x => console.log('Exercise 8:', x), 100);
// trailingThrottle('first');  // Logs immediately
// trailingThrottle('middle'); // Saved
// trailingThrottle('last');   // Saved, logs after 100ms

/*
 * SOLUTION 8:
 *
 * function throttleTrailing(fn, limit) {
 *     let lastArgs = null;
 *     let timeoutId = null;
 *
 *     return function(...args) {
 *         if (!timeoutId) {
 *             fn.apply(this, args);
 *             timeoutId = setTimeout(() => {
 *                 if (lastArgs) {
 *                     fn.apply(this, lastArgs);
 *                     lastArgs = null;
 *                 }
 *                 timeoutId = null;
 *             }, limit);
 *         } else {
 *             lastArgs = args;
 *         }
 *     };
 * }
 */

/**
 * EXERCISE 9: Interval Counter
 *
 * Create a function 'countUp' that:
 * - Takes (callback, intervalMs, count)
 * - Calls callback with (currentCount) every intervalMs
 * - Stops after count iterations
 * - Returns a function to stop early
 */

// TODO: Implement countUp
function countUp(callback, intervalMs, count) {
  // Your code here
}

// Test
// const stop = countUp(n => console.log('Exercise 9: Count', n), 100, 5);
// // Logs 1, 2, 3, 4, 5 then stops
// // Or: setTimeout(() => stop(), 250); // Stop early

/*
 * SOLUTION 9:
 *
 * function countUp(callback, intervalMs, count) {
 *     let current = 0;
 *     const intervalId = setInterval(() => {
 *         current++;
 *         callback(current);
 *         if (current >= count) {
 *             clearInterval(intervalId);
 *         }
 *     }, intervalMs);
 *
 *     return function stop() {
 *         clearInterval(intervalId);
 *     };
 * }
 */

/**
 * EXERCISE 10: Polling Function
 *
 * Create an async function 'pollUntil' that:
 * - Takes (asyncFn, conditionFn, intervalMs, timeoutMs)
 * - Calls asyncFn every intervalMs
 * - Stops when conditionFn(result) returns true
 * - Rejects if timeoutMs is exceeded
 */

// TODO: Implement pollUntil
async function pollUntil(asyncFn, conditionFn, intervalMs, timeoutMs) {
  // Your code here
}

// Test
// let pollCount = 0;
// pollUntil(
//     async () => ({ ready: ++pollCount >= 3, count: pollCount }),
//     result => result.ready,
//     50,
//     1000
// ).then(r => console.log('Exercise 10:', r));

/*
 * SOLUTION 10:
 *
 * async function pollUntil(asyncFn, conditionFn, intervalMs, timeoutMs) {
 *     const startTime = Date.now();
 *
 *     while (Date.now() - startTime < timeoutMs) {
 *         const result = await asyncFn();
 *         if (conditionFn(result)) {
 *             return result;
 *         }
 *         await delay(intervalMs);
 *     }
 *
 *     throw new Error('Polling timeout');
 * }
 */

/**
 * EXERCISE 11: Timer Manager
 *
 * Create a class 'TimerManager' that:
 * - Has method 'setTimeout(callback, delay)' returning id
 * - Has method 'setInterval(callback, interval)' returning id
 * - Has method 'clear(id)' to cancel any timer
 * - Has method 'clearAll()' to cancel all timers
 * - Tracks all active timers
 */

// TODO: Implement TimerManager
class TimerManager {
  // Your code here
}

// Test
// const tm = new TimerManager();
// const id1 = tm.setTimeout(() => console.log('Exercise 11: timeout'), 100);
// const id2 = tm.setInterval(() => console.log('Exercise 11: interval'), 50);
// setTimeout(() => tm.clear(id2), 150);
// console.log('Exercise 11: Active timers', tm.getActiveCount?.() || 'N/A');

/*
 * SOLUTION 11:
 *
 * class TimerManager {
 *     constructor() {
 *         this.timers = new Map();
 *         this.nextId = 0;
 *     }
 *
 *     setTimeout(callback, delay) {
 *         const id = ++this.nextId;
 *         const timerId = setTimeout(() => {
 *             this.timers.delete(id);
 *             callback();
 *         }, delay);
 *         this.timers.set(id, { type: 'timeout', timerId });
 *         return id;
 *     }
 *
 *     setInterval(callback, interval) {
 *         const id = ++this.nextId;
 *         const timerId = setInterval(callback, interval);
 *         this.timers.set(id, { type: 'interval', timerId });
 *         return id;
 *     }
 *
 *     clear(id) {
 *         const timer = this.timers.get(id);
 *         if (timer) {
 *             if (timer.type === 'timeout') {
 *                 clearTimeout(timer.timerId);
 *             } else {
 *                 clearInterval(timer.timerId);
 *             }
 *             this.timers.delete(id);
 *         }
 *     }
 *
 *     clearAll() {
 *         for (const [id] of this.timers) {
 *             this.clear(id);
 *         }
 *     }
 *
 *     getActiveCount() {
 *         return this.timers.size;
 *     }
 * }
 */

/**
 * EXERCISE 12: Rate Limiter
 *
 * Create a class 'RateLimiter' that:
 * - Constructor takes (maxCalls, perMs)
 * - Has async method 'acquire()' that waits if limit reached
 * - Has method 'execute(asyncFn)' that rate-limits fn execution
 */

// TODO: Implement RateLimiter
class RateLimiter {
  // Your code here
}

// Test
// const limiter = new RateLimiter(2, 200);
// Promise.all([1, 2, 3, 4].map(i =>
//     limiter.execute(async () => {
//         console.log(`Exercise 12: Call ${i} at ${Date.now() % 1000}ms`);
//         return i;
//     })
// )).then(results => console.log('Exercise 12: Results', results));

/*
 * SOLUTION 12:
 *
 * class RateLimiter {
 *     constructor(maxCalls, perMs) {
 *         this.maxCalls = maxCalls;
 *         this.perMs = perMs;
 *         this.calls = [];
 *     }
 *
 *     async acquire() {
 *         const now = Date.now();
 *         this.calls = this.calls.filter(time => now - time < this.perMs);
 *
 *         if (this.calls.length >= this.maxCalls) {
 *             const waitTime = this.calls[0] + this.perMs - now;
 *             await delay(waitTime);
 *             return this.acquire();
 *         }
 *
 *         this.calls.push(now);
 *     }
 *
 *     async execute(asyncFn) {
 *         await this.acquire();
 *         return asyncFn();
 *     }
 * }
 */

/**
 * EXERCISE 13: Self-Correcting Interval
 *
 * Create a function 'accurateInterval' that:
 * - Takes (callback, interval, count)
 * - Calls callback(tickNumber, drift) count times
 * - Corrects for drift to maintain accurate timing
 * - Returns stop function
 */

// TODO: Implement accurateInterval
function accurateInterval(callback, interval, count) {
  // Your code here
}

// Test
// const stopAccurate = accurateInterval(
//     (tick, drift) => console.log(`Exercise 13: Tick ${tick}, drift ${drift}ms`),
//     100,
//     3
// );

/*
 * SOLUTION 13:
 *
 * function accurateInterval(callback, interval, count) {
 *     let expected = Date.now() + interval;
 *     let tick = 0;
 *     let stopped = false;
 *
 *     function step() {
 *         if (stopped || tick >= count) return;
 *
 *         const drift = Date.now() - expected;
 *         tick++;
 *         callback(tick, drift);
 *
 *         if (tick < count) {
 *             expected += interval;
 *             setTimeout(step, Math.max(0, interval - drift));
 *         }
 *     }
 *
 *     setTimeout(step, interval);
 *
 *     return function stop() {
 *         stopped = true;
 *     };
 * }
 */

/**
 * EXERCISE 14: Countdown Timer
 *
 * Create a class 'Countdown' that:
 * - Constructor takes (seconds, onTick, onComplete)
 * - Has methods: start(), pause(), resume(), reset()
 * - onTick receives remaining seconds
 * - onComplete is called when countdown reaches 0
 */

// TODO: Implement Countdown
class Countdown {
  // Your code here
}

// Test
// const cd = new Countdown(
//     3,
//     s => console.log(`Exercise 14: ${s}s remaining`),
//     () => console.log('Exercise 14: Complete!')
// );
// cd.start();

/*
 * SOLUTION 14:
 *
 * class Countdown {
 *     constructor(seconds, onTick, onComplete) {
 *         this.initial = seconds;
 *         this.remaining = seconds;
 *         this.onTick = onTick;
 *         this.onComplete = onComplete;
 *         this.intervalId = null;
 *     }
 *
 *     start() {
 *         if (this.intervalId) return;
 *         this.onTick(this.remaining);
 *
 *         this.intervalId = setInterval(() => {
 *             this.remaining--;
 *
 *             if (this.remaining <= 0) {
 *                 this.stop();
 *                 this.onComplete();
 *             } else {
 *                 this.onTick(this.remaining);
 *             }
 *         }, 1000);
 *     }
 *
 *     pause() {
 *         this.stop();
 *     }
 *
 *     resume() {
 *         if (this.remaining > 0) {
 *             this.start();
 *         }
 *     }
 *
 *     reset() {
 *         this.stop();
 *         this.remaining = this.initial;
 *     }
 *
 *     stop() {
 *         if (this.intervalId) {
 *             clearInterval(this.intervalId);
 *             this.intervalId = null;
 *         }
 *     }
 * }
 */

/**
 * EXERCISE 15: Task Scheduler
 *
 * Create a class 'Scheduler' that:
 * - Has method 'schedule(name, callback, options)'
 *   options: { delay?, interval?, repeat? }
 * - Has method 'cancel(name)'
 * - Has method 'cancelAll()'
 * - Has method 'list()' returning scheduled task names
 * - Handles both one-time and repeating tasks
 */

// TODO: Implement Scheduler
class Scheduler {
  // Your code here
}

// Test
// const sched = new Scheduler();
// sched.schedule('once', () => console.log('Exercise 15: Once'), { delay: 100 });
// sched.schedule('repeat', () => console.log('Exercise 15: Repeat'), { interval: 50, repeat: 3 });
// console.log('Exercise 15: Scheduled', sched.list());

/*
 * SOLUTION 15:
 *
 * class Scheduler {
 *     constructor() {
 *         this.tasks = new Map();
 *     }
 *
 *     schedule(name, callback, options = {}) {
 *         const { delay = 0, interval, repeat = Infinity } = options;
 *
 *         if (interval) {
 *             let count = 0;
 *             const id = setInterval(() => {
 *                 count++;
 *                 callback(count);
 *                 if (count >= repeat) {
 *                     this.cancel(name);
 *                 }
 *             }, interval);
 *             this.tasks.set(name, { type: 'interval', id });
 *         } else {
 *             const id = setTimeout(() => {
 *                 callback();
 *                 this.tasks.delete(name);
 *             }, delay);
 *             this.tasks.set(name, { type: 'timeout', id });
 *         }
 *     }
 *
 *     cancel(name) {
 *         const task = this.tasks.get(name);
 *         if (task) {
 *             if (task.type === 'interval') {
 *                 clearInterval(task.id);
 *             } else {
 *                 clearTimeout(task.id);
 *             }
 *             this.tasks.delete(name);
 *         }
 *     }
 *
 *     cancelAll() {
 *         for (const name of this.tasks.keys()) {
 *             this.cancel(name);
 *         }
 *     }
 *
 *     list() {
 *         return Array.from(this.tasks.keys());
 *     }
 * }
 */

// ============================================================
// RUN TESTS
// ============================================================

async function runAllTests() {
  console.log('Testing Timer and Interval Exercises...\n');
  // Uncomment individual tests above to verify solutions
}

// runAllTests();

console.log('Timer and Interval Exercises loaded.');
console.log('Uncomment tests to verify your solutions.');
Exercises - JavaScript Tutorial | DeepML