javascript

exercises

exercises.js
/**
 * Async Patterns - Exercises
 * Practice implementing common async patterns
 */

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// =============================================================================
// EXERCISE 1: Sequential with Early Exit
// Implement sequential processing that stops on condition
// =============================================================================

/*
 * TODO: Create processUntil(items, processor, shouldStop)
 * - Process items one at a time
 * - Stop early if shouldStop(result) returns true
 * - Return { results: [...], stopped: boolean, stoppedAt: index|null }
 */

async function processUntil(items, processor, shouldStop) {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
async function processUntil(items, processor, shouldStop) {
    const results = [];
    let stopped = false;
    let stoppedAt = null;
    
    for (let i = 0; i < items.length; i++) {
        const result = await processor(items[i], i);
        results.push(result);
        
        if (shouldStop(result, i)) {
            stopped = true;
            stoppedAt = i;
            break;
        }
    }
    
    return { results, stopped, stoppedAt };
}
*/

// =============================================================================
// EXERCISE 2: Parallel with Progress
// Implement parallel processing with progress tracking
// =============================================================================

/*
 * TODO: Create parallelWithProgress(tasks, onProgress)
 * - Run all tasks in parallel
 * - Call onProgress({ completed, total, percent, latestResult })
 * - Return all results when complete
 */

async function parallelWithProgress(tasks, onProgress) {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
async function parallelWithProgress(tasks, onProgress) {
    let completed = 0;
    const total = tasks.length;
    const results = new Array(total);
    
    const wrappedTasks = tasks.map(async (task, index) => {
        const result = await task();
        completed++;
        results[index] = result;
        
        onProgress({
            completed,
            total,
            percent: Math.round((completed / total) * 100),
            latestResult: result,
            index
        });
        
        return result;
    });
    
    await Promise.all(wrappedTasks);
    return results;
}
*/

// =============================================================================
// EXERCISE 3: Smart Batching
// Implement adaptive batch sizing based on success/failure
// =============================================================================

/*
 * TODO: Create smartBatch(items, processor, options)
 * - options: { initialBatchSize, minBatchSize, maxBatchSize }
 * - Start with initialBatchSize
 * - Increase batch size on successful batches
 * - Decrease batch size on failed batches
 * - Return { results, errors, finalBatchSize }
 */

async function smartBatch(items, processor, options = {}) {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
async function smartBatch(items, processor, options = {}) {
    const {
        initialBatchSize = 5,
        minBatchSize = 1,
        maxBatchSize = 20
    } = options;
    
    let batchSize = initialBatchSize;
    let index = 0;
    const results = [];
    const errors = [];
    
    while (index < items.length) {
        const batch = items.slice(index, index + batchSize);
        console.log(`Processing batch of ${batch.length} (size: ${batchSize})`);
        
        try {
            const batchResults = await Promise.all(
                batch.map(item => processor(item))
            );
            results.push(...batchResults);
            
            // Success - increase batch size
            batchSize = Math.min(batchSize + 2, maxBatchSize);
        } catch (error) {
            errors.push({ error, batchStart: index, batchSize });
            
            // Failure - decrease batch size
            batchSize = Math.max(Math.floor(batchSize / 2), minBatchSize);
            
            // Process individually for this batch
            for (const item of batch) {
                try {
                    results.push(await processor(item));
                } catch (e) {
                    errors.push({ error: e, item });
                }
            }
        }
        
        index += batch.length;
    }
    
    return { results, errors, finalBatchSize: batchSize };
}
*/

// =============================================================================
// EXERCISE 4: Debounce with Leading Edge
// Implement debounce with both leading and trailing options
// =============================================================================

/*
 * TODO: Create advancedDebounce(fn, wait, options)
 * - options: { leading, trailing, maxWait }
 * - leading: execute on first call
 * - trailing: execute after wait
 * - maxWait: maximum time to wait before forced execution
 * - Return debounced function with .cancel() method
 */

function advancedDebounce(fn, wait, options = {}) {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
function advancedDebounce(fn, wait, options = {}) {
    const { leading = false, trailing = true, maxWait = null } = options;
    
    let timeout = null;
    let maxTimeout = null;
    let lastArgs = null;
    let lastThis = null;
    let lastCallTime = null;
    let lastInvokeTime = 0;
    let result;
    
    function invoke() {
        const args = lastArgs;
        const thisArg = lastThis;
        
        lastArgs = lastThis = null;
        lastInvokeTime = Date.now();
        result = fn.apply(thisArg, args);
        return result;
    }
    
    function shouldInvoke(time) {
        const timeSinceLastCall = time - lastCallTime;
        const timeSinceLastInvoke = time - lastInvokeTime;
        
        return lastCallTime === null || 
               timeSinceLastCall >= wait ||
               (maxWait !== null && timeSinceLastInvoke >= maxWait);
    }
    
    function timerExpired() {
        const time = Date.now();
        
        if (shouldInvoke(time)) {
            return trailingEdge();
        }
        
        timeout = setTimeout(timerExpired, wait - (time - lastCallTime));
    }
    
    function trailingEdge() {
        timeout = null;
        
        if (trailing && lastArgs) {
            return invoke();
        }
        
        lastArgs = lastThis = null;
        return result;
    }
    
    function debounced(...args) {
        const time = Date.now();
        const isInvoking = shouldInvoke(time);
        
        lastArgs = args;
        lastThis = this;
        lastCallTime = time;
        
        if (isInvoking) {
            if (timeout === null) {
                // Leading edge
                lastInvokeTime = time;
                timeout = setTimeout(timerExpired, wait);
                
                if (leading) {
                    return invoke();
                }
            }
            
            if (maxWait !== null && maxTimeout === null) {
                maxTimeout = setTimeout(() => {
                    maxTimeout = null;
                    if (lastArgs) invoke();
                }, maxWait);
            }
        }
        
        return result;
    }
    
    debounced.cancel = function() {
        if (timeout) clearTimeout(timeout);
        if (maxTimeout) clearTimeout(maxTimeout);
        timeout = maxTimeout = null;
        lastArgs = lastThis = lastCallTime = null;
        lastInvokeTime = 0;
    };
    
    debounced.flush = function() {
        if (timeout) {
            return trailingEdge();
        }
        return result;
    };
    
    return debounced;
}
*/

// =============================================================================
// EXERCISE 5: Async Pipeline with Error Handling
// Build a robust async pipeline with middleware support
// =============================================================================

/*
 * TODO: Create Pipeline class that:
 * - Chains async functions
 * - Supports error handling at each step
 * - Allows middleware (before/after each step)
 * - Can be branched based on conditions
 */

class Pipeline {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class Pipeline {
    constructor() {
        this.steps = [];
        this.errorHandlers = new Map();
        this.middleware = { before: [], after: [] };
    }
    
    pipe(fn, options = {}) {
        this.steps.push({ fn, options, name: options.name || fn.name });
        return this;
    }
    
    catch(stepName, handler) {
        this.errorHandlers.set(stepName, handler);
        return this;
    }
    
    before(fn) {
        this.middleware.before.push(fn);
        return this;
    }
    
    after(fn) {
        this.middleware.after.push(fn);
        return this;
    }
    
    branch(condition, truePipeline, falsePipeline) {
        this.steps.push({
            fn: async (value, context) => {
                if (await condition(value, context)) {
                    return truePipeline.execute(value, context);
                }
                return falsePipeline ? falsePipeline.execute(value, context) : value;
            },
            options: { name: 'branch' },
            name: 'branch'
        });
        return this;
    }
    
    async execute(initialValue, context = {}) {
        let value = initialValue;
        
        for (const step of this.steps) {
            // Run before middleware
            for (const mw of this.middleware.before) {
                await mw(value, step.name, context);
            }
            
            try {
                value = await step.fn(value, context);
            } catch (error) {
                const handler = this.errorHandlers.get(step.name);
                
                if (handler) {
                    value = await handler(error, value, context);
                } else {
                    throw error;
                }
            }
            
            // Run after middleware
            for (const mw of this.middleware.after) {
                await mw(value, step.name, context);
            }
        }
        
        return value;
    }
    
    static create() {
        return new Pipeline();
    }
}
*/

// =============================================================================
// EXERCISE 6: Rate Limiter
// Implement a token bucket rate limiter
// =============================================================================

/*
 * TODO: Create RateLimiter class that:
 * - Uses token bucket algorithm
 * - Allows burst capacity
 * - Supports waiting for tokens
 * - Tracks usage statistics
 */

class RateLimiter {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class RateLimiter {
    constructor(options = {}) {
        this.maxTokens = options.maxTokens || 10;
        this.refillRate = options.refillRate || 1; // tokens per second
        this.tokens = this.maxTokens;
        this.lastRefill = Date.now();
        
        this.stats = {
            allowed: 0,
            rejected: 0,
            waited: 0
        };
    }
    
    refill() {
        const now = Date.now();
        const elapsed = (now - this.lastRefill) / 1000;
        
        this.tokens = Math.min(
            this.maxTokens,
            this.tokens + (elapsed * this.refillRate)
        );
        
        this.lastRefill = now;
    }
    
    tryAcquire(tokens = 1) {
        this.refill();
        
        if (this.tokens >= tokens) {
            this.tokens -= tokens;
            this.stats.allowed++;
            return true;
        }
        
        this.stats.rejected++;
        return false;
    }
    
    async acquire(tokens = 1) {
        this.refill();
        
        if (this.tokens >= tokens) {
            this.tokens -= tokens;
            this.stats.allowed++;
            return;
        }
        
        // Calculate wait time
        const needed = tokens - this.tokens;
        const waitTime = (needed / this.refillRate) * 1000;
        
        this.stats.waited++;
        await delay(waitTime);
        
        this.refill();
        this.tokens -= tokens;
        this.stats.allowed++;
    }
    
    async wrap(fn, tokens = 1) {
        await this.acquire(tokens);
        return fn();
    }
    
    getStats() {
        return {
            ...this.stats,
            currentTokens: this.tokens,
            utilization: this.stats.allowed / (this.stats.allowed + this.stats.rejected)
        };
    }
}
*/

// =============================================================================
// EXERCISE 7: Async Event Emitter
// Build an async-aware event emitter
// =============================================================================

/*
 * TODO: Create AsyncEventEmitter class that:
 * - Supports async event handlers
 * - Can wait for all handlers to complete
 * - Supports once() for single-fire events
 * - Can await specific events with waitFor()
 */

class AsyncEventEmitter {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class AsyncEventEmitter {
    constructor() {
        this.listeners = new Map();
        this.onceListeners = new Map();
    }
    
    on(event, handler) {
        if (!this.listeners.has(event)) {
            this.listeners.set(event, []);
        }
        this.listeners.get(event).push(handler);
        return () => this.off(event, handler);
    }
    
    once(event, handler) {
        if (!this.onceListeners.has(event)) {
            this.onceListeners.set(event, []);
        }
        this.onceListeners.get(event).push(handler);
    }
    
    off(event, handler) {
        const handlers = this.listeners.get(event);
        if (handlers) {
            const index = handlers.indexOf(handler);
            if (index > -1) {
                handlers.splice(index, 1);
            }
        }
    }
    
    async emit(event, ...args) {
        const results = [];
        
        // Regular listeners
        const handlers = this.listeners.get(event) || [];
        for (const handler of handlers) {
            results.push(await handler(...args));
        }
        
        // Once listeners
        const onceHandlers = this.onceListeners.get(event) || [];
        this.onceListeners.delete(event);
        for (const handler of onceHandlers) {
            results.push(await handler(...args));
        }
        
        return results;
    }
    
    async emitParallel(event, ...args) {
        const handlers = [
            ...(this.listeners.get(event) || []),
            ...(this.onceListeners.get(event) || [])
        ];
        
        this.onceListeners.delete(event);
        
        return Promise.all(handlers.map(h => h(...args)));
    }
    
    waitFor(event, timeout = null) {
        return new Promise((resolve, reject) => {
            let timeoutId;
            
            if (timeout) {
                timeoutId = setTimeout(() => {
                    reject(new Error(`Timeout waiting for event: ${event}`));
                }, timeout);
            }
            
            this.once(event, (...args) => {
                if (timeoutId) clearTimeout(timeoutId);
                resolve(args);
            });
        });
    }
}
*/

// =============================================================================
// EXERCISE 8: Request Coalescing
// Implement request deduplication and coalescing
// =============================================================================

/*
 * TODO: Create RequestCoalescer that:
 * - Deduplicates identical in-flight requests
 * - Coalesces multiple requests into batch
 * - Has configurable batch window
 * - Properly distributes results to callers
 */

class RequestCoalescer {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class RequestCoalescer {
    constructor(batchFn, options = {}) {
        this.batchFn = batchFn;
        this.batchWindow = options.batchWindow || 10;
        this.maxBatchSize = options.maxBatchSize || 100;
        
        this.pending = new Map(); // For deduplication
        this.batch = [];          // Current batch
        this.batchTimeout = null;
        this.batchResolvers = [];
    }
    
    async request(key, params) {
        // Check for in-flight request with same key
        if (this.pending.has(key)) {
            return this.pending.get(key);
        }
        
        // Create promise for this request
        const promise = new Promise((resolve, reject) => {
            this.batch.push({ key, params, resolve, reject });
            
            if (this.batch.length >= this.maxBatchSize) {
                this.flushBatch();
            } else if (!this.batchTimeout) {
                this.batchTimeout = setTimeout(
                    () => this.flushBatch(),
                    this.batchWindow
                );
            }
        });
        
        this.pending.set(key, promise);
        
        // Clean up pending after resolution
        promise.finally(() => this.pending.delete(key));
        
        return promise;
    }
    
    async flushBatch() {
        if (this.batchTimeout) {
            clearTimeout(this.batchTimeout);
            this.batchTimeout = null;
        }
        
        const currentBatch = this.batch;
        this.batch = [];
        
        if (currentBatch.length === 0) return;
        
        try {
            // Execute batch function
            const batchParams = currentBatch.map(item => ({
                key: item.key,
                params: item.params
            }));
            
            const results = await this.batchFn(batchParams);
            
            // Distribute results
            currentBatch.forEach((item, index) => {
                if (results[index].error) {
                    item.reject(results[index].error);
                } else {
                    item.resolve(results[index].value);
                }
            });
        } catch (error) {
            // Reject all on batch error
            currentBatch.forEach(item => item.reject(error));
        }
    }
}
*/

// =============================================================================
// EXERCISE 9: Async Pool with Priorities
// Implement a priority-aware async pool
// =============================================================================

/*
 * TODO: Create PriorityPool that:
 * - Processes high-priority tasks first
 * - Supports multiple priority levels
 * - Maintains order within same priority
 * - Allows priority boosting
 */

class PriorityPool {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class PriorityPool {
    constructor(concurrency = 3) {
        this.concurrency = concurrency;
        this.running = 0;
        this.queues = new Map(); // priority -> queue
        this.taskIds = new Map();
        this.nextId = 0;
    }
    
    enqueue(task, priority = 0) {
        const id = this.nextId++;
        
        return new Promise((resolve, reject) => {
            if (!this.queues.has(priority)) {
                this.queues.set(priority, []);
            }
            
            const item = { id, task, resolve, reject, priority };
            this.queues.get(priority).push(item);
            this.taskIds.set(id, item);
            
            this.process();
        });
    }
    
    boostPriority(taskId, newPriority) {
        const item = this.taskIds.get(taskId);
        if (!item) return false;
        
        // Remove from current queue
        const currentQueue = this.queues.get(item.priority);
        const index = currentQueue.findIndex(i => i.id === taskId);
        if (index > -1) {
            currentQueue.splice(index, 1);
        }
        
        // Add to new queue
        item.priority = newPriority;
        if (!this.queues.has(newPriority)) {
            this.queues.set(newPriority, []);
        }
        this.queues.get(newPriority).push(item);
        
        return true;
    }
    
    getNextTask() {
        // Get highest priority (largest number first)
        const priorities = [...this.queues.keys()].sort((a, b) => b - a);
        
        for (const priority of priorities) {
            const queue = this.queues.get(priority);
            if (queue.length > 0) {
                return queue.shift();
            }
        }
        
        return null;
    }
    
    async process() {
        while (this.running < this.concurrency) {
            const item = this.getNextTask();
            if (!item) break;
            
            this.running++;
            
            try {
                const result = await item.task();
                item.resolve(result);
            } catch (error) {
                item.reject(error);
            } finally {
                this.taskIds.delete(item.id);
                this.running--;
                this.process();
            }
        }
    }
    
    getQueueSizes() {
        const sizes = {};
        for (const [priority, queue] of this.queues) {
            sizes[priority] = queue.length;
        }
        return sizes;
    }
}
*/

// =============================================================================
// EXERCISE 10: Async State Machine
// Implement an async-aware state machine
// =============================================================================

/*
 * TODO: Create AsyncStateMachine that:
 * - Supports async transition actions
 * - Validates state transitions
 * - Emits events on state changes
 * - Supports guards (conditions for transitions)
 */

class AsyncStateMachine {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class AsyncStateMachine {
    constructor(config) {
        this.states = config.states;
        this.currentState = config.initial;
        this.context = config.context || {};
        this.listeners = [];
    }
    
    on(callback) {
        this.listeners.push(callback);
        return () => {
            this.listeners = this.listeners.filter(l => l !== callback);
        };
    }
    
    emit(event, data) {
        this.listeners.forEach(l => l(event, data));
    }
    
    getState() {
        return this.currentState;
    }
    
    getContext() {
        return { ...this.context };
    }
    
    can(action) {
        const state = this.states[this.currentState];
        return state && action in state.on;
    }
    
    async transition(action, payload = {}) {
        const state = this.states[this.currentState];
        
        if (!state || !state.on || !state.on[action]) {
            throw new Error(`Invalid action "${action}" for state "${this.currentState}"`);
        }
        
        const transition = state.on[action];
        const targetState = typeof transition === 'string' 
            ? transition 
            : transition.target;
        
        // Check guard
        if (transition.guard) {
            const allowed = await transition.guard(this.context, payload);
            if (!allowed) {
                this.emit('guardFailed', { action, state: this.currentState });
                return false;
            }
        }
        
        // Run exit action
        if (state.onExit) {
            await state.onExit(this.context, payload);
        }
        
        const previousState = this.currentState;
        
        // Run transition action
        if (transition.action) {
            this.context = await transition.action(this.context, payload) || this.context;
        }
        
        // Update state
        this.currentState = targetState;
        
        // Run entry action
        const newState = this.states[targetState];
        if (newState.onEnter) {
            await newState.onEnter(this.context, payload);
        }
        
        this.emit('transition', {
            from: previousState,
            to: targetState,
            action,
            context: this.context
        });
        
        return true;
    }
}
*/

// =============================================================================
// TEST YOUR IMPLEMENTATIONS
// =============================================================================

async function runTests() {
  console.log('Testing Async Patterns...\n');

  // Test Exercise 1
  console.log('1. Sequential with Early Exit:');
  // Test code here

  // Test Exercise 2
  console.log('\n2. Parallel with Progress:');
  // Test code here

  // Continue for other exercises...

  console.log('\nAll tests complete!');
}

// Uncomment to run tests
// runTests();
Exercises - JavaScript Tutorial | DeepML