javascript
exercises
exercises.js⚡javascript
/**
* Web Workers - Exercises
* Practice working with background threads
*
* Note: These exercises use mock workers for Node.js compatibility.
* In browsers, you would use actual Web Workers.
*/
// Mock Worker for exercises
class MockWorker {
constructor(workerFn) {
this.workerFn = workerFn;
this.onmessage = null;
this.onerror = null;
}
postMessage(data) {
setTimeout(() => {
try {
const result = this.workerFn(data);
if (this.onmessage) {
this.onmessage({ data: result });
}
} catch (error) {
if (this.onerror) {
this.onerror(error);
}
}
}, 0);
}
terminate() {
this.onmessage = null;
this.onerror = null;
}
}
// =============================================================================
// EXERCISE 1: Promise-Based Worker Wrapper
// Create a wrapper that makes workers return promises
// =============================================================================
/*
* TODO: Create promisifyWorker(workerFn) that:
* - Returns a function that accepts data and returns a Promise
* - Resolves with worker result
* - Rejects on worker error
* - Supports timeout option
*/
function promisifyWorker(workerFn, options = {}) {
// YOUR CODE HERE:
}
// SOLUTION:
/*
function promisifyWorker(workerFn, options = {}) {
const { timeout = 30000 } = options;
return function(data) {
return new Promise((resolve, reject) => {
const worker = new MockWorker(workerFn);
const timeoutId = setTimeout(() => {
worker.terminate();
reject(new Error(`Worker timeout after ${timeout}ms`));
}, timeout);
worker.onmessage = (event) => {
clearTimeout(timeoutId);
resolve(event.data);
};
worker.onerror = (error) => {
clearTimeout(timeoutId);
reject(error);
};
worker.postMessage(data);
});
};
}
*/
// =============================================================================
// EXERCISE 2: Worker Pool with Load Balancing
// Implement a pool that distributes work evenly
// =============================================================================
/*
* TODO: Create WorkerPool class that:
* - Manages multiple workers
* - Distributes tasks round-robin or least-busy
* - Tracks worker utilization
* - Supports dynamic scaling
*/
class WorkerPool {
// YOUR CODE HERE:
}
// SOLUTION:
/*
class WorkerPool {
constructor(workerFn, options = {}) {
this.workerFn = workerFn;
this.minWorkers = options.minWorkers || 1;
this.maxWorkers = options.maxWorkers || 8;
this.workers = [];
this.taskQueue = [];
this.roundRobinIndex = 0;
// Initialize minimum workers
for (let i = 0; i < this.minWorkers; i++) {
this.addWorker();
}
}
addWorker() {
if (this.workers.length >= this.maxWorkers) return false;
const worker = {
instance: new MockWorker(this.workerFn),
busy: false,
tasksCompleted: 0,
currentTask: null
};
this.workers.push(worker);
return true;
}
removeWorker() {
if (this.workers.length <= this.minWorkers) return false;
const idleWorker = this.workers.find(w => !w.busy);
if (idleWorker) {
idleWorker.instance.terminate();
this.workers = this.workers.filter(w => w !== idleWorker);
return true;
}
return false;
}
getNextWorker(strategy = 'round-robin') {
if (strategy === 'round-robin') {
let attempts = 0;
while (attempts < this.workers.length) {
const worker = this.workers[this.roundRobinIndex];
this.roundRobinIndex = (this.roundRobinIndex + 1) % this.workers.length;
if (!worker.busy) {
return worker;
}
attempts++;
}
return null; // All busy
}
if (strategy === 'least-busy') {
return this.workers
.filter(w => !w.busy)
.sort((a, b) => a.tasksCompleted - b.tasksCompleted)[0] || null;
}
return null;
}
async execute(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
const worker = this.getNextWorker();
if (worker) {
this.runTask(worker, task);
} else {
// Scale up if possible
if (this.addWorker()) {
this.runTask(this.workers[this.workers.length - 1], task);
} else {
// Queue the task
this.taskQueue.push(task);
}
}
});
}
runTask(worker, task) {
worker.busy = true;
worker.currentTask = task;
worker.instance.onmessage = (event) => {
worker.busy = false;
worker.tasksCompleted++;
worker.currentTask = null;
task.resolve(event.data);
// Process queued task
if (this.taskQueue.length > 0) {
const nextTask = this.taskQueue.shift();
this.runTask(worker, nextTask);
}
};
worker.instance.onerror = (error) => {
worker.busy = false;
worker.currentTask = null;
task.reject(error);
// Process queued task
if (this.taskQueue.length > 0) {
const nextTask = this.taskQueue.shift();
this.runTask(worker, nextTask);
}
};
worker.instance.postMessage(task.data);
}
getStats() {
return {
totalWorkers: this.workers.length,
busyWorkers: this.workers.filter(w => w.busy).length,
idleWorkers: this.workers.filter(w => !w.busy).length,
queuedTasks: this.taskQueue.length,
totalTasksCompleted: this.workers.reduce((sum, w) => sum + w.tasksCompleted, 0)
};
}
terminate() {
this.workers.forEach(w => w.instance.terminate());
this.workers = [];
this.taskQueue = [];
}
}
*/
// =============================================================================
// EXERCISE 3: Worker RPC System
// Implement a remote procedure call system over workers
// =============================================================================
/*
* TODO: Create WorkerRPC class that:
* - Allows calling named methods on worker
* - Supports request/response with IDs
* - Handles timeouts and errors
* - Supports method registration on worker side
*/
class WorkerRPC {
// YOUR CODE HERE:
}
// SOLUTION:
/*
class WorkerRPC {
constructor(methods = {}) {
this.methods = methods;
this.pending = new Map();
this.nextId = 0;
// Worker handler function
this.workerHandler = (message) => {
const { id, method, args } = message;
if (!this.methods[method]) {
return { id, error: `Unknown method: ${method}` };
}
try {
const result = this.methods[method](...args);
return { id, result };
} catch (error) {
return { id, error: error.message };
}
};
this.worker = new MockWorker(this.workerHandler);
this.worker.onmessage = (event) => {
const { id, result, error } = event.data;
if (this.pending.has(id)) {
const { resolve, reject } = this.pending.get(id);
this.pending.delete(id);
if (error) {
reject(new Error(error));
} else {
resolve(result);
}
}
};
}
register(name, fn) {
this.methods[name] = fn;
}
call(method, ...args) {
const id = this.nextId++;
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pending.delete(id);
reject(new Error(`RPC timeout: ${method}`));
}, 5000);
this.pending.set(id, {
resolve: (result) => {
clearTimeout(timeout);
resolve(result);
},
reject: (error) => {
clearTimeout(timeout);
reject(error);
}
});
this.worker.postMessage({ id, method, args });
});
}
createProxy() {
return new Proxy({}, {
get: (_, method) => {
return (...args) => this.call(method, ...args);
}
});
}
}
*/
// =============================================================================
// EXERCISE 4: Parallel Map with Workers
// Implement parallel array processing using workers
// =============================================================================
/*
* TODO: Create parallelMap(array, mapFn, options) that:
* - Splits array into chunks
* - Processes chunks in parallel workers
* - Combines results in correct order
* - Supports progress callback
*/
async function parallelMap(array, mapFn, options = {}) {
// YOUR CODE HERE:
}
// SOLUTION:
/*
async function parallelMap(array, mapFn, options = {}) {
const {
workerCount = 4,
chunkSize = Math.ceil(array.length / workerCount),
onProgress = () => {}
} = options;
// Create worker function
const workerFn = (data) => {
const { chunk, startIndex } = data;
// Note: In real worker, mapFn would need to be serialized
const results = chunk.map((item, i) => ({
index: startIndex + i,
value: item * 2 // Simplified - in real code, use mapFn
}));
return results;
};
// Create chunks
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push({
chunk: array.slice(i, i + chunkSize),
startIndex: i
});
}
// Process in parallel
const pool = new WorkerPool(workerFn, { maxWorkers: workerCount });
let completed = 0;
const chunkResults = await Promise.all(
chunks.map(async (chunk) => {
const result = await pool.execute(chunk);
completed++;
onProgress({
completed,
total: chunks.length,
percent: Math.round((completed / chunks.length) * 100)
});
return result;
})
);
pool.terminate();
// Combine results in order
const results = new Array(array.length);
for (const chunkResult of chunkResults) {
for (const { index, value } of chunkResult) {
results[index] = value;
}
}
return results;
}
*/
// =============================================================================
// EXERCISE 5: Worker Coordinator
// Coordinate multiple workers for a complex task
// =============================================================================
/*
* TODO: Create WorkerCoordinator class that:
* - Manages workers with different roles
* - Supports pipeline processing (output of one → input of next)
* - Handles fan-out/fan-in patterns
* - Tracks overall progress
*/
class WorkerCoordinator {
// YOUR CODE HERE:
}
// SOLUTION:
/*
class WorkerCoordinator {
constructor() {
this.stages = new Map();
this.connections = [];
}
addStage(name, workerFn, options = {}) {
this.stages.set(name, {
workerFn,
instances: options.instances || 1,
workers: [],
options
});
return this;
}
connect(from, to) {
this.connections.push({ from, to });
return this;
}
async initialize() {
for (const [name, stage] of this.stages) {
for (let i = 0; i < stage.instances; i++) {
stage.workers.push(new MockWorker(stage.workerFn));
}
}
}
async execute(initialData, onProgress = () => {}) {
const results = new Map();
let data = initialData;
// Topological sort of stages
const order = this.getExecutionOrder();
let completed = 0;
for (const stageName of order) {
const stage = this.stages.get(stageName);
if (stage.instances === 1) {
// Single worker
data = await this.runWorker(stage.workers[0], data);
} else {
// Fan-out/fan-in
const chunks = this.splitData(data, stage.instances);
const partialResults = await Promise.all(
chunks.map((chunk, i) =>
this.runWorker(stage.workers[i], chunk)
)
);
data = this.mergeResults(partialResults);
}
results.set(stageName, data);
completed++;
onProgress({
stage: stageName,
completed,
total: order.length,
percent: Math.round((completed / order.length) * 100)
});
}
return { final: data, intermediate: results };
}
runWorker(worker, data) {
return new Promise((resolve, reject) => {
worker.onmessage = (e) => resolve(e.data);
worker.onerror = reject;
worker.postMessage(data);
});
}
getExecutionOrder() {
// Simple order based on connections
const order = [];
const visited = new Set();
const visit = (name) => {
if (visited.has(name)) return;
visited.add(name);
for (const conn of this.connections) {
if (conn.to === name) {
visit(conn.from);
}
}
order.push(name);
};
for (const name of this.stages.keys()) {
visit(name);
}
return order;
}
splitData(data, count) {
if (!Array.isArray(data)) return [data];
const chunks = [];
const chunkSize = Math.ceil(data.length / count);
for (let i = 0; i < count; i++) {
chunks.push(data.slice(i * chunkSize, (i + 1) * chunkSize));
}
return chunks;
}
mergeResults(results) {
if (results.every(Array.isArray)) {
return results.flat();
}
return results;
}
terminate() {
for (const stage of this.stages.values()) {
stage.workers.forEach(w => w.terminate());
}
}
}
*/
// =============================================================================
// EXERCISE 6: Shared State Simulation
// Simulate shared state between workers
// =============================================================================
/*
* TODO: Create SharedState class that:
* - Simulates shared memory between workers
* - Supports atomic operations
* - Notifies workers of state changes
* - Handles race conditions
*/
class SharedState {
// YOUR CODE HERE:
}
// SOLUTION:
/*
class SharedState {
constructor(initialState = {}) {
this.state = new Map(Object.entries(initialState));
this.subscribers = new Map();
this.locks = new Map();
this.version = 0;
}
get(key) {
return this.state.get(key);
}
set(key, value) {
this.version++;
this.state.set(key, value);
this.notify(key, value);
return value;
}
async atomicUpdate(key, updateFn) {
// Acquire lock
while (this.locks.get(key)) {
await new Promise(resolve => setTimeout(resolve, 1));
}
this.locks.set(key, true);
try {
const oldValue = this.state.get(key);
const newValue = updateFn(oldValue);
this.set(key, newValue);
return newValue;
} finally {
this.locks.delete(key);
}
}
compareAndSwap(key, expectedValue, newValue) {
if (this.state.get(key) === expectedValue) {
this.set(key, newValue);
return true;
}
return false;
}
increment(key, delta = 1) {
const current = this.state.get(key) || 0;
return this.set(key, current + delta);
}
subscribe(key, callback) {
if (!this.subscribers.has(key)) {
this.subscribers.set(key, []);
}
this.subscribers.get(key).push(callback);
return () => {
const subs = this.subscribers.get(key);
const index = subs.indexOf(callback);
if (index > -1) subs.splice(index, 1);
};
}
notify(key, value) {
const subs = this.subscribers.get(key) || [];
subs.forEach(callback => callback(value, key, this.version));
}
getState() {
return Object.fromEntries(this.state);
}
}
*/
// =============================================================================
// EXERCISE 7: Worker Health Monitor
// Monitor worker health and auto-recover
// =============================================================================
/*
* TODO: Create WorkerHealthMonitor class that:
* - Monitors worker responsiveness
* - Detects stuck/dead workers
* - Auto-restarts failed workers
* - Reports health statistics
*/
class WorkerHealthMonitor {
// YOUR CODE HERE:
}
// SOLUTION:
/*
class WorkerHealthMonitor {
constructor(pool, options = {}) {
this.pool = pool;
this.checkInterval = options.checkInterval || 5000;
this.timeout = options.timeout || 10000;
this.maxRestarts = options.maxRestarts || 3;
this.workerStats = new Map();
this.intervalId = null;
}
start() {
this.intervalId = setInterval(() => this.checkHealth(), this.checkInterval);
return this;
}
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
async checkHealth() {
console.log('Checking worker health...');
for (const worker of this.pool.workers || []) {
const stats = this.getWorkerStats(worker);
// Check if worker is responsive
const isResponsive = await this.pingWorker(worker);
if (!isResponsive) {
stats.failedChecks++;
if (stats.failedChecks >= 3 && stats.restarts < this.maxRestarts) {
console.log('Restarting unresponsive worker');
await this.restartWorker(worker);
stats.restarts++;
stats.failedChecks = 0;
}
} else {
stats.failedChecks = 0;
stats.lastHealthy = Date.now();
}
}
}
async pingWorker(worker) {
return new Promise((resolve) => {
const pingTimeout = setTimeout(() => resolve(false), this.timeout);
// Simulate ping
setTimeout(() => {
clearTimeout(pingTimeout);
resolve(true);
}, 100);
});
}
async restartWorker(worker) {
worker.instance.terminate();
worker.instance = new MockWorker(this.pool.workerFn);
worker.busy = false;
}
getWorkerStats(worker) {
if (!this.workerStats.has(worker)) {
this.workerStats.set(worker, {
failedChecks: 0,
restarts: 0,
lastHealthy: Date.now()
});
}
return this.workerStats.get(worker);
}
getHealthReport() {
const report = {
totalWorkers: this.pool.workers?.length || 0,
healthy: 0,
unhealthy: 0,
restarted: 0,
workers: []
};
for (const [worker, stats] of this.workerStats) {
const isHealthy = stats.failedChecks === 0;
if (isHealthy) report.healthy++;
else report.unhealthy++;
if (stats.restarts > 0) report.restarted++;
report.workers.push({
healthy: isHealthy,
failedChecks: stats.failedChecks,
restarts: stats.restarts,
lastHealthy: new Date(stats.lastHealthy).toISOString()
});
}
return report;
}
}
*/
// =============================================================================
// EXERCISE 8: Task Scheduler
// Schedule and prioritize worker tasks
// =============================================================================
/*
* TODO: Create TaskScheduler class that:
* - Schedules tasks with priorities
* - Supports delayed execution
* - Allows task cancellation
* - Handles dependencies between tasks
*/
class TaskScheduler {
// YOUR CODE HERE:
}
// SOLUTION:
/*
class TaskScheduler {
constructor(pool) {
this.pool = pool;
this.tasks = new Map();
this.nextId = 0;
this.running = new Set();
}
schedule(task, options = {}) {
const {
priority = 0,
delay = 0,
dependencies = [],
onComplete = () => {},
onError = () => {}
} = options;
const id = this.nextId++;
const taskInfo = {
id,
task,
priority,
scheduledTime: Date.now() + delay,
dependencies,
onComplete,
onError,
status: 'pending',
result: null,
error: null
};
this.tasks.set(id, taskInfo);
if (delay > 0) {
setTimeout(() => this.tryRun(id), delay);
} else {
this.tryRun(id);
}
return {
id,
cancel: () => this.cancel(id),
getStatus: () => this.getTaskStatus(id),
result: () => this.getTaskResult(id)
};
}
async tryRun(id) {
const taskInfo = this.tasks.get(id);
if (!taskInfo || taskInfo.status !== 'pending') return;
// Check dependencies
for (const depId of taskInfo.dependencies) {
const dep = this.tasks.get(depId);
if (!dep || dep.status !== 'completed') {
// Wait for dependencies
setTimeout(() => this.tryRun(id), 100);
return;
}
}
// Check if it's time
if (Date.now() < taskInfo.scheduledTime) {
setTimeout(() => this.tryRun(id), taskInfo.scheduledTime - Date.now());
return;
}
// Run the task
taskInfo.status = 'running';
this.running.add(id);
try {
const result = await this.pool.execute(taskInfo.task);
taskInfo.status = 'completed';
taskInfo.result = result;
taskInfo.onComplete(result);
} catch (error) {
taskInfo.status = 'failed';
taskInfo.error = error;
taskInfo.onError(error);
} finally {
this.running.delete(id);
}
}
cancel(id) {
const taskInfo = this.tasks.get(id);
if (taskInfo && taskInfo.status === 'pending') {
taskInfo.status = 'cancelled';
return true;
}
return false;
}
getTaskStatus(id) {
const taskInfo = this.tasks.get(id);
return taskInfo ? taskInfo.status : null;
}
getTaskResult(id) {
const taskInfo = this.tasks.get(id);
if (!taskInfo) return null;
if (taskInfo.status === 'completed') {
return { success: true, result: taskInfo.result };
} else if (taskInfo.status === 'failed') {
return { success: false, error: taskInfo.error };
}
return null;
}
getSchedulerStats() {
const stats = {
pending: 0,
running: 0,
completed: 0,
failed: 0,
cancelled: 0
};
for (const task of this.tasks.values()) {
stats[task.status]++;
}
return stats;
}
}
*/
// =============================================================================
// TEST YOUR IMPLEMENTATIONS
// =============================================================================
async function runTests() {
console.log('Testing Web Worker Implementations...\n');
// Test worker function
const testWorkerFn = (data) => {
if (data.delay) {
// Simulate delay (in real worker, would use actual setTimeout)
}
return data.value * 2;
};
console.log('1. Testing Promise Worker:');
// Test code here
console.log('\n2. Testing Worker Pool:');
// Test code here
console.log('\n3. Testing Worker RPC:');
// Test code here
console.log('\nAll tests complete!');
}
// Uncomment to run tests
// runTests();