0

Let's say I have 60 tasks that do something that takes at least 30 minutes each. Now I want to only run 5 at a time, so when 1 of those 5 finishes another task gets executed till all are finished.

What is the cleanest way to do this? And is there a name for this pattern?

  • 1
    https://en.wikipedia.org/wiki/Thread_pool – Peter Feb 09 '18 at 17:35
  • To run no more that 5 tasks at the time (one of the 5 finishes then start the next one) you can see [this answer](https://stackoverflow.com/a/48001650/1641941) To run 5 tasks and when all are finished run the next 5 you can see [this code](https://jsfiddle.net/mowadco2/) – HMR Feb 10 '18 at 05:58

2 Answers2

3

I recommend bluebird, which has a Promise.map method, which you can configure to use a concurrency, in your case of 5.

DaniGuardiola
  • 861
  • 6
  • 18
0

One solution is to write a helper class to do this for you. I've had to use this pattern multiple times myself, so I have this utility class that manages a set of promises and allows no more than the specified limit to run at once:

class PromiseLimitQueue {

  constructor(limit) {
    if (Math.floor(limit) <= 0) throw new Error("Limit must be a positive integer.");
    
    this._limit = Math.floor(limit);
    this._head = [];
    this._queue = [];
  }

  // The promFunc must be a function that returns a promise, not a promise itself.
  // Method returns a new promise that resolves when the promise that promFunc
  // returns has run and resolved.
  enqueue(promFunc) {
    return new Promise((resolve, reject) => {
      const newFunc = () => promFunc().then(resolve).catch(reject);
      this._queue.push(newFunc);

      if (this._head.length < this._limit) this._pullNext();
    });
  }

  _pullNext() {
    const next = this._queue.shift();
    if (!next) return;

    const prom = next();
    const onDone = () => {
      const index = this._head.indexOf(prom);
      if (index !== -1) this._head.splice(index, 1);
      this._pullNext();
    }

    prom.then(onDone, onDone);
    this._head.push(prom);
  }
}

// Usage Example:

// Helper function returns a promise that resolves after 'num' milliseconds.
function timer(num) {
  return new Promise(resolve => {
    setTimeout(resolve, num)
  })
}

// Create a new limit queue, give desired limit (in this case, 5);
const limitQueue = new PromiseLimitQueue(5);

// Queue up 20 1-second timers, observe that only 5 ever run at once:
for (let i = 0; i < 20; i++) {
  limitQueue.enqueue(() => timer(1000)).then(() => console.log(`Timer ${i} Resolved.`))
}
CRice
  • 29,968
  • 4
  • 57
  • 70