# Async Task Scheduler (Concurrency Limiter) ## Summary Implement a `TaskScheduler` class that allows adding asynchronous tasks. The scheduler should execute tasks but ensure that no more than N tasks run continuously at the same time. As soon as one finishes, the next pending task should start. ## Requirements * `new TaskScheduler(concurrency)` - The class `TaskScheduler` should accept concurrency (integer) in the constructor. This is the maximum number of tasks which be executed simultaneously. * `TaskScheduler.addTask(taskFn) : Promise` - Adds `taskFn` to task queue. Returns a Promise that resolves/rejects when the actual task finishes. `taskFn` is a function that returns a Promise. * `TaskScheduler.play()` - Puts the execution in 'play' mode. * `TaskScheduler.pause()` - Puts the execution in 'paused' mode. * In 'play' mode, new tasks from the queue will get picked up and processed. * In 'paused' mode, no new tasks will get processed. Already running tasks will run till finish. * By default, new `TaskScheduler` instances should be in 'paused' mode. * Tasks should be executed in First-In-First-Out (FIFO) order. * (In play mode) If the number of running tasks is less than concurrency, start new task immediately. * (In play mode) If the concurrency limit is reached, queue the task. * When a running task completes (resolves or rejects), automatically pick the next task from the queue. * ## Example ```JavaScript class TaskScheduler { constructor(concurrency) { this.concurrency = concurrency; this.running = 0; this.queue = []; } addTask(taskFn) { return new Promise((resolve, reject) => { const taskWrapper = async () => { this.running++; try { const result = await taskFn(); resolve(result); } catch (err) { reject(err); } finally { this.running--; this.runNext(); } }; if (this.running < this.concurrency) { taskWrapper(); } else { this.queue.push(taskWrapper); } }); } runNext() { if (this.queue.length > 0 && this.running < this.concurrency) { const nextTask = this.queue.shift(); nextTask(); } } } // Usage const scheduler = new TaskScheduler(2); const delay = (ms, val) => new Promise(r => setTimeout(() => r(val), ms)); scheduler.addTask(() => delay(1000, 'A')).then(console.log); scheduler.addTask(() => delay(500, 'B')).then(console.log); scheduler.addTask(() => delay(300, 'C')).then(console.log); // Output: B (at 500ms), A (at 1000ms), C (at 1300ms) - C waits for B to finish. ``` ## Follow-ups * Priority Queue: Modify addTask to accept a priority level (High, Low). Each priority level gets its own queue. Tasks from the High-priority queue should be picked up first. Only when it is empty, Low-priority tasks will get picked up. * Cancellation: Return a cancel function from addTask (alongwith the promise). If cancelled while in the queue, it should never run. * Error Handling: What happens if taskFn throws synchronously instead of returning a Promise? Ensure the scheduler doesn't crash.