2

I was solving this interesting javascript problem (interview question) and I got stuck on how I can implement this using promises.

Problem:

Write a scheduler in JavaScript that accepts max number of concurrent tasks as a parameter and schedules tasks (each task may take arbitrary time to complete).

Please note that we need to only execute "n" (concurrent) tasks at a time before proceeding to execute other tasks.

This is my implementation:

var exampleTaskA = function () {
    setTimeout(function () {
        console.log('Task A Done');
    }, 1000);
};

function TaskRunner(concurrency) {
    this.limit = concurrency;
    this.store = [];
    this.len = this.store.length;
}

TaskRunner.prototype.push = function (task) {
    this.store.push(task);
    function getWorker(store, limit) {
        if(!store.length) return;

        if(store.length <= limit) {
            const cur = store.shift();
            if(cur) cur();
            getWorker(store, limit);
        }
    }

    getWorker(this.store, this.limit);
}

var task = new TaskRunner(2);
console.log(task.push(exampleTaskA));
console.log(task.push(exampleTaskA));
console.log(task.push(exampleTaskA)); 
console.log(task.push(exampleTaskA));
console.log(task.push(exampleTaskA));
console.log(task.push(exampleTaskA));
console.log(task.push(exampleTaskA));

How can I use promises / async await to implement this? Should I wrap everything around a promise before pushing?

Can someone enlighten?

TechnoCorner
  • 4,879
  • 10
  • 43
  • 81
  • the problem appears odd, since almost nothing in JS actually runs "concurrently" unless it's an inherently async operation (e.g. AJAX) – Alnitak Aug 14 '18 at 21:55
  • This was asked to me in one of the interview questions in top tech companies so I'm trying to solve it using Promises @Alnitak – TechnoCorner Aug 14 '18 at 21:55
  • I believe in this case, the concurrency means that the user has to execute only "X" items from the queue at a time. – TechnoCorner Aug 14 '18 at 21:57
  • Unless it's a queue of async operations, you can't. If you ever pass a task that's actually synchronous it'll run to the exclusion of all others. – Alnitak Aug 14 '18 at 21:59
  • They are .. please take a look at the task.. it's a `setTimeout` task() .. Correct me if i'm wrong. – TechnoCorner Aug 14 '18 at 22:01
  • what you're asking is probably not suitable for this Q&A format – Alnitak Aug 14 '18 at 22:02
  • Possible duplicate of [Is setTimeout a good solution to do async functions with javascript?](https://stackoverflow.com/questions/19626680/is-settimeout-a-good-solution-to-do-async-functions-with-javascript) – Sphinx Aug 14 '18 at 22:08
  • @TechnoCorner Can you change the task function to return a promise? – Mark Aug 14 '18 at 22:28
  • @MarkMeyer good plan - you can't hope to have a Promise-based scheduler if your tasks aren't already returning Promises. – Alnitak Aug 14 '18 at 22:32
  • Right @Alnitak -- it's just not really clear in this question what is given and what one is supposed to write. The name `exampleTaskA` suggests (at least to me) that that is an example of what the tasks look like (in which case there's not much hope). – Mark Aug 14 '18 at 22:36
  • @MarkMeyer I can actually return a promise, but how would you resolve it? `exampleTaskA` could be anything coming from another API call. I just want to throttle the execution to `this.limit` .. Appreciate your time folks. – TechnoCorner Aug 14 '18 at 22:51

3 Answers3

6

So if you can return a promise from your task, you can tie into the the promise's then() to alert you when the task is finished and when you can start another.

Here's an example similar to yours with a couple changes: we don't care about the length of the queue — you only want to know how many active jobs are present. So you can increment active when starting jobs a decrement it when jobs are done.

There are a lot of ways, I'm sure to do this, but here's the outline of one idea:

const exampleTaskA = (name) => new Promise(resolve => setTimeout(function() {
  console.log(`Task ${name} Done`);
  resolve()
}, Math.floor(Math.random() * 2000)))

function TaskRunner(concurrency) {
  this.limit = concurrency;
  this.store = [];
  this.active = 0;
}

TaskRunner.prototype.next = function() {
  if (this.store.length) this.runTask(...this.store.shift())
}

TaskRunner.prototype.runTask = function(task, name) {
  this.active++
  console.log(`Scheduling task ${name} current active: ${this.active}`)
  task(name).then(() => {
    this.active--
    console.log(`Task ${name} returned, current active: ${this.active}`)
    this.next()
  })
}
TaskRunner.prototype.push = function(task, name) {
  if (this.active < this.limit) this.runTask(task, name)
  else {
    console.log(`queuing task ${name}`)
    this.store.push([task, name])
  }
}

var task = new TaskRunner(2);
task.push(exampleTaskA, 1)
task.push(exampleTaskA, 2)
task.push(exampleTaskA, 3)
task.push(exampleTaskA, 4)
task.push(exampleTaskA, 5)
task.push(exampleTaskA, 6)
task.push(exampleTaskA, 7)
Mark
  • 90,562
  • 7
  • 108
  • 148
0
class TaskScheduler {
    constructor(concurrency) {
        this.limit = concurrency;
        this.active = 0;
        this.pool = [];
    }
    push(task) {
        this.pool.push(task);
        if (this.active < this.limit) {
            this.active += 1;
            this.execute(this.pool.shift());
        }
    }
    execute(task) {
        task().then(val => {
            console.log(`${val} executed`);
            this.active -= 1
            if (this.pool.length && this.active < this.limit) {
                this.execute(this.pool.shift());
            }
        });
    }
}

const task = () => new Promise((res, rej) => setTimeout(() => res('task'), 4000));
const task2 = () => new Promise((res, rej) => setTimeout(() => res('task'), 200));

const scheduler = new TaskScheduler(2);
scheduler.push(task);
scheduler.push(task2);
scheduler.push(task2);
scheduler.push(task2);
scheduler.push(task);
scheduler.push(task);
scheduler.push(task);
scheduler.push(task);
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 19 '22 at 17:39
-1

window.addEventListener("load", function() { // this is without promises

function Task(name) {
    this.isDone = false;
    this.name = name;
}

Task.prototype.run = function() {
    setTimeout(()=>{
        this.isDone = true;
    }, 3000);
}

function TaskScheduler(limit) {
    this.limit = limit;
    this.active = 0;
    this.pendingTasks = [];
    this.runningTasks = [];
}

TaskScheduler.prototype.runTask = function(task) {
    this.active++;
    task.run();
}

TaskScheduler.prototype.init = function() {
    var interval = setInterval(() => {
        // check and clean running tasks
        this.runningTasks = this.runningTasks.filter((task) => {
            if (task.isDone) {
                this.active--;
            }
            return !task.isDone;
        });

        while(this.pendingTasks.length) {
            if (this.active < this.limit) {
                var task = this.pendingTasks.pop();
                this.runTask(task);
                this.runningTasks.push(task);
            } else {
                break;
            }
        }

        if (!this.pendingTasks.length) {
            clearInterval(interval);
        }
    }, 0);
}

TaskScheduler.prototype.push = function(task) {
    this.pendingTasks.push(task);
}

var taskSecheduler = new TaskScheduler(2);
taskSecheduler.push(new Task(1));
taskSecheduler.push(new Task(2));
taskSecheduler.push(new Task(3));
taskSecheduler.push(new Task(4));
taskSecheduler.push(new Task(5));
taskSecheduler.push(new Task(6));
taskSecheduler.init();

});