2

Hi I am trying to implement two classes that deal with asynchronous tasks in JavaScript: one class is called Task which run the task with setTimeout another class is called TaskManager that takes capacity to limit the numbers of task can be executed at once. I thought if I could just call the loop function recursively just to keep checking if one job is done, so I can proceed to the next job. Can someone give me any guidances where I can fix this? (also, I will be greatly appreciated if anyone can provide some similar JS async exercise/website to practice)

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);
learner
  • 23
  • 2
  • You can use a generator to achieve the expected result. – guest271314 Feb 27 '19 at 08:37
  • Javascript is strictly single threaded. No tasks will run in parallel, rather they run concurrently (giving the illusion of being parallel). Therefore, any infinite loop such as your recursive `loop()` function will cause `setTimeout` to never run thus no tasks will ever execute. The solution is that `loop()` itself must be `setTimeout`ed – slebetman Feb 27 '19 at 08:39
  • You can read my answer to this other question to understand how the asynchronous "engine" in javascript work: https://stackoverflow.com/questions/29883525/i-know-that-callback-function-runs-asynchronously-but-why/29885509#29885509 – slebetman Feb 27 '19 at 08:42

1 Answers1

1

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);
trincot
  • 263,463
  • 30
  • 215
  • 251