I am trying to implement two classes that can deal with asynchronous tasks in JavaScript:
- Class
Task
: mimics the execution of a task withsetTimeout
. Once the timer expires, the task is considered completed. - Class
TaskManager
: has acapacity
parameter to limit the numbers of tasks that can be executing in parallel.
I thought if I could just call the loop
function recursively, just to keep checking if one job is done, I could proceed to the next job. But this leads immediately to a “Maximum call stack size exceeded” error.
Can someone explain how I can fix this?
class Task { constructor(time) { this.time = time; this.running = 0; } run(limit, jobs, index) { setTimeout(() => { console.log('hello', index); this.done(limit, jobs, index); }, this.time); } done(limit, jobs, index) { jobs.splice(index, 1); console.log(jobs); } } class TaskManager { constructor(capacity) { this.capacity = capacity; this.jobs = []; this.index = 0; this.running = 0; this.pending = []; } push(tk) { this.jobs.push(tk); this.index += 1; const loop = () => { if (this.jobs.length === 0) { return; } if (this.jobs.length <= this.capacity) { this.running += 1; tk.run(this.capacity, this.jobs, this.index-1); return; } loop(); } loop(); } } const task = new Task(100); const task1 = new Task(200); const task2 = new Task(400); const task3 = new Task(5000); const task4 = new Task(6000); const manager = new TaskManager(3); manager.push(task); manager.push(task1); manager.push(task2); manager.push(task3); manager.push(task4);
Advertisement
Answer
You should not implement the busy loop, as that will block the event loop and so no user UI events or setTimeout
events will be processed.
Instead respond to asynchronous events.
If you let the setTimeout
callback resolve a Promise, it is not so hard to do.
I modified your script quite drastically. Here is the result:
class Task { constructor(id, time) { this.id = id; this.time = time; } run() { console.log(this + ' launched.'); return new Promise(resolve => { setTimeout(() => { console.log(this + ' completed.'); resolve(); }, this.time); }); } toString() { return `Task ${this.id}[${this.time}ms]`; } } class TaskManager { constructor(capacity) { this.capacity = capacity; this.waiting = []; this.running = []; } push(tk) { this.waiting.push(tk); if (this.running.length < this.capacity) { this.next(); } else { console.log(tk + ' put on hold.'); } } next() { const task = this.waiting.shift(); if (!task) { if (!this.running.length) { console.log("All done."); } return; // No new tasks } this.running.push(task); const runningTask = task.run(); console.log("Currently running: " + this.running); runningTask.then(() => { this.running = this.running.filter(t => t !== task); console.log("Currently running: " + this.running); this.next(); }); } } const a = new Task('A', 100); const b = new Task('B', 200); const c = new Task('C', 400); const d = new Task('D', 5000); const e = new Task('E', 6000); const manager = new TaskManager(3); manager.push(a); manager.push(b); manager.push(c); manager.push(d); manager.push(e);