Как я могу ограничить обещание Q concurrency?

Как написать метод, который ограничивает Q обещание concurrency?

Например, у меня есть метод spawnProcess. Он возвращает обещание Q.
Я хочу не более 5 процессов, созданных за раз, но прозрачно для вызывающего кода.

Мне нужно реализовать функцию с сигнатурой

function limitConcurrency(promiseFactory, limit)

который я могу назвать как

spawnProcess = limitConcurrency(spawnProcess, 5);

// use spawnProcess as usual

Я уже начал работать над своей версией, но мне интересно, имеет ли кто-нибудь краткий вариант выполнения, с которым я могу проверить.

Ответ 1

У меня есть библиотека, которая делает это для вас https://github.com/ForbesLindesay/throat

Вы можете использовать его через браузер или загрузить автономную сборку из brcdn (https://www.brcdn.org/?module=throat&version=latest) и добавить ее как тег script.

Затем (предполагая, что конструктор Promise полиполнен или реализован в вашей среде) вы можете сделать:

//remove this line if using standalone build
var throat = require('throat');

function limitConcurrency(promiseFactory, limit) {
  var fn = throat(promiseFactory, limit);
  return function () {
    return Q(fn.apply(this, arguments));
  }
}

Вы можете просто вызвать throat(promiseFactory, limit) напрямую, но это вернет promise обещание, а не обещание Q.

Мне также очень нравится использовать его с array.map.

// only allow 3 parallel downloads
var downloadedItems = Q.all(items.map(throat(download, 3)));

Ответ 2

Кажется, это работает для меня.

Я не уверен, могу ли я это упростить. Рекурсия в scheduleNextJob необходима, чтобы running < limit и limit++ всегда выполнялись в одном тике.

Также доступен как сущность.

'use strict';

var Q = require('q');

/**
 * Constructs a function that proxies to promiseFactory
 * limiting the count of promises that can run simultaneously.
 * @param promiseFactory function that returns promises.
 * @param limit how many promises are allowed to be running at the same time.
 * @returns function that returns a promise that eventually proxies to promiseFactory.
 */
function limitConcurrency(promiseFactory, limit) {
  var running = 0,
      semaphore;

  function scheduleNextJob() {
    if (running < limit) {
      running++;
      return Q();
    }

    if (!semaphore) {
      semaphore = Q.defer();
    }

    return semaphore.promise
      .finally(scheduleNextJob);
  }

  function processScheduledJobs() {
    running--;

    if (semaphore && running < limit) {
      semaphore.resolve();
      semaphore = null;
    }
  }

  return function () {
    var args = arguments;

    function runJob() {
      return promiseFactory.apply(this, args);
    }

    return scheduleNextJob()
      .then(runJob)
      .finally(processScheduledJobs);
  };
}

module.exports = {
  limitConcurrency: limitConcurrency
}

Ответ 3

Deferred реализация обещания gate, которая работает именно так:

spawnProcess = deferred.gate(spawnProcess, 5);    

Ответ 4

Я написал небольшую библиотеку, чтобы сделать это: https://github.com/suprememoocow/qlimit

Он чрезвычайно прост в использовании и специально разработан для работы с Q promises:

var qlimit = require('qlimit');
var limit = qlimit(2); // 2 being the maximum concurrency

// Using the same example as above
return Q.all(items.map(limit(function(item, index, collection) { 
  return performOperationOnItem(item);
}));

Его также можно использовать для ограничения concurrency на конкретный ресурс, например:

var qlimit = require('qlimit');
var limit = qlimit(2); // 2 being the maximum concurrency

var fetchSomethingFromEasilyOverwhelmedBackendServer = limit(function(id) {
  // Emulating the backend service
  return Q.delay(1000)
    .thenResolve({ hello: 'world' }); 
});