Jan 19, 2016

Run async functions in parallel with concurrent limit in Javascript

Suppose you have an asynchronous function in Javascript and you want to limit the maximum calls get executed at once and queued all others. This article explains how to implement it in simple way. Let's start with queue like structure:


var MAX_CONCURRENCY_LIMIT = 3;
var IDLE_DELAY = 500;

var tasks = {
  queue : [],
  totalActive :0,
  add : function(item){
    if (!item || item.length == 0){
      return;
    }
    var q = this.queue;
    if (q.indexOf(item) == -1)
      q.push(item);
  },
  done : function(item){
    this.totalActive--;
  },
  getNext : function(){
    this.totalActive++;
    //It will remove the returned item from queue automatically
    return this.queue.shift();
  },
  isPending : function(){
    return this.queue.length > 0;
  }
};

MAX_CONCURRENCY_LIMIT is constant defines maximum number of tasks executing at the same time(parallel limit).

IDLE_DELAY is delay in millisecond to check if any task in queue.

To manage the concurrency and execute the function, here is the code


function runTask(){

  if (tasks.isPending() && tasks.totalActive < MAX_CONCURRENCY_LIMIT){
    var item = tasks.getNext();
    console.log('Processing ' + item + ' Total Active Items ' + tasks.totalActive);
    myTask(item);
    runTask();
  }

  else if(tasks.totalActive >= MAX_CONCURRENCY_LIMIT){
    console.log('Hold State');
    setTimeout(function(){ runTask(); }, IDLE_DELAY);
  }

  else{
    setTimeout(function(){ runTask(); }, IDLE_DELAY);
  }
}
runTask();

For simplicity, let's take following asynchronous function.


function myTask(item){
    setTimeout(function(){ 
      console.log(item + ' completed.');  
      tasks.done(item); 
    }, 500);
}

To test the parallel limit, let's use following code:


for(var i=1;i<=10;i++){
  tasks.add('Item: ' + i);
}

Output:

Processing Item: 1 Total Active Items 1
Processing Item: 2 Total Active Items 2
Processing Item: 3 Total Active Items 3
Hold State
Item: 1 completed.
Item: 2 completed.
Item: 3 completed.
Processing Item: 4 Total Active Items 1
Processing Item: 5 Total Active Items 2
Processing Item: 6 Total Active Items 3
Hold State
Item: 4 completed.
Item: 5 completed.
Item: 6 completed.
Processing Item: 7 Total Active Items 1
Processing Item: 8 Total Active Items 2
Processing Item: 9 Total Active Items 3
Hold State
Item: 7 completed.
Item: 8 completed.
Item: 9 completed.
Processing Item: 10 Total Active Items 1
Item: 10 completed.

You can see 3-3 items are queued and processed. Once it reaches max limit then hold state comes.

Now let's do some more fun for testing


function myTask(item){
    setTimeout(function(){ 
      console.log(item + ' completed.');  
      tasks.done(item); 
    }, Math.random()*1000);
}

now this function takes randomize time to get completed.

Outout:
Processing Item: 1 Total Active Items 1
Processing Item: 2 Total Active Items 2
Processing Item: 3 Total Active Items 3
Hold State
Item: 1 completed.
Processing Item: 4 Total Active Items 3
Hold State
Item: 2 completed.
Item: 4 completed.
Item: 3 completed.
Processing Item: 5 Total Active Items 1
Processing Item: 6 Total Active Items 2
Processing Item: 7 Total Active Items 3
Hold State
Item: 7 completed.
Processing Item: 8 Total Active Items 3
Hold State
Item: 5 completed.
Item: 6 completed.
Processing Item: 9 Total Active Items 2
Processing Item: 10 Total Active Items 3
Hold State
Item: 8 completed.
Item: 9 completed.
Item: 10 completed.

You can see if any item is completed, another item is picked from queue for processing. On max limit, hold state comes. You can customize it according to your requirement.

Hope, it helps. Enjoy Javascript !!