在Node js中限制Q promise并发性

时间:2014-11-18 11:04:28

标签: javascript node.js concurrency q

有没有办法限制在节点js中一次执行的并发Q promises数量?

我正在构建一个网络剪贴簿,它必须请求并解析更多3000多个页面并且没有限制我做出的一些请求没有按时响应,因此连接休息和所需的响应(html代码)变得不可用。

为了反击行动,我发现限制了我的问题消失的请求数量。


我尝试过以下方法,但无济于事:

我需要请求一个url数组,一次只执行1个请求,并且当数组中的所有url都已完成时,然后将结果返回到数组中。

function processWebsite() {
  //computed by this stage
  urls = [u1,u2,u3,u4,l5,u6,u7,u8,u9];

  var promises = throttle(urls,1,myfunction);

  // myfunction returns a Q promise and takes a considerable 
  // amount of time to resolve (approximately 2-5 minutes)

  Q.all(promises).then(function(results){
      //work with the results of the promises array
  });
}

4 个答案:

答案 0 :(得分:3)

我会这样做,它将迭代每个URL,构建一个在前一个URL完成时运行的promise,并使用一组请求结果解析。

return urls.reduce(function(acc, url){
    return acc.then(function(results)
        return myfunction(url).then(function(requestResult){
             return results.concat(requestResult)
        });
    });
}, Q.resolve([]));

你也可以把它变成帮手:

var results = map(urls, myfunction);

function map(items, fn){
    return items.reduce(function(acc, item){
        return acc.then(function(results)
            return fn(item).then(function(result){
                 return results.concat(result)
            });
        });
    }, Q.resolve([])
}

注意,bluebird promise库有一个帮助器来简化这类事情。

return Bluebird.map(urls, myfunction, {concurrency: 1});

答案 1 :(得分:1)

这是我为Q制作限制map功能的尝试。

function qMap(items, worker, concurrent) {
    var result = Q.defer();
    var work = [];
    var working = 0;
    var done = 0;

    concurrent = parseInt(concurrent, 10) || 1;

    function getNextIndex() {
        var i;
        for (i = 0; i < items.length; i++) {
            if (typeof work[i] === "undefined") return i;
        }
    }
    function doneWorking() {
        working--;
        done++;
        result.notify( +((100 * done / items.length).toFixed(1)) );
        if (!startWorking() && done === items.length) {
            result.resolve(work);
        }
    }
    function startWorking() {
        var index = getNextIndex();
        if (typeof index !== "undefined" && working < concurrent) {
            working++;
            work[index] = worker(items[index]).finally(doneWorking);
            return true;
        }
    }
    while (startWorking());
    return result.promise;
}

接受

  • 要处理的items数组(在您的情况下为网址),
  • a worker(必须是接受项目并返回承诺的函数)
  • 以及在任何给定时间处理的concurrent项的最大值。

返回

  • 承诺和
  • 当所有工人完成时,
  • 解决了一系列既定的承诺。

它不会失败,您必须检查各个承诺以确定操作的整体状态。

在你的情况下你会像那样使用它,例如有15个并发请求:

// myfunction returns a Q promise and takes a considerable 
// amount of time to resolve (approximately 2-5 minutes)

qMap(urls, myfunction, 15)
.progress(function (percentDone) {
    console.log("progress: " + percentDone);
})
.done(function (urlPromises) {
    console.log("all done: " + urlPromises);
});

答案 2 :(得分:0)

您可以在then()

中申请新网址
myFunction(urls[0]).then(function(result) {
  myFunction(urls[1]).then(function(result) {
    myFunction(urls[2]).then(function(result) {
      ...
    });
  });
});

当然,这将是它的动态行为。一旦承诺得到解决,我就会保留队列并将单个网址出列。然后提出另一个请求。也许有一个哈希对象将网址与结果相关联。

第二步:

var urls = ...;
var limit = ...;
var dequeue = function() {
  return an array containing up to limit
};

var myFunction = function(dequeue) {
  var urls = dequeue();

  $q.all(process urls);
};

myFunction(dequeue).then(function(result) {
  myFunction(dequeue).then(function(result) {
    myFunction(dequeue).then(function(result) {
      ...
    });
  });
});

答案 3 :(得分:0)

可以使用递归来解决。

这个想法是,最初您将发送允许的最大请求数,并且这些请求中的每一个都应在完成后递归地继续发送自己。

function processWebsite(urls, concurrentRequestsLimit) {
    return new Promise(resolve => {
        var pages = [];
        var index = 0;

        function recursiveFetch() {
            if (index === urls.length) {
                return;
            }
            fetch(urls[index++]).then(r => {
                pages.push(r.text());
                if (pages.length === urls.length) {
                    resolve(pages);
                } else {
                    recursiveFetch();
                }
            });
        }

        for (var i = 0; i < concurrentRequestsLimit; i++) {
            recursiveFetch();
        }
    });
}

var urls = [
    'http://www.example.com/page_1',
    'http://www.example.com/page_2',
    'http://www.example.com/page_3',
    ...
    'http://www.example.com/page_3000'
];
processWebsite(urls, 5).then(pages => {
   //process all 3000 pages here
});