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