关于javascript:Node js中的Limit Q promise并发

Limit Q promise concurrency in Node js

有什么方法可以限制在节点js中一次执行的并发Q承诺的数量吗?

我正在构建一个Web爬网程序,该爬网程序必须请求和解析更多3000多个页面,并且不限制我提出的某些请求,这些请求没有及时得到响应,因此连接中断了,所需的响应(html代码)变得不可用。

为了解决这个问题,我发现限制问题的请求数量已经消失了。

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

  • Q承诺中的并发限制-节点
  • 如何限制Q许诺并发?
  • https://gist.github.com/gaearon/7930162
  • https://github.com/ForbesLindesay/throat

我需要请求一个url数组,一次仅在阵列中的所有url完成后执行一次请求,然后将结果返回到数组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
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
  });
}

我会这样做,它将遍历每个URL,构建一个当前一个结束时运行的诺言链,并以一系列请求结果进行解析。

1
2
3
4
5
6
7
return urls.reduce(function(acc, url){
    return acc.then(function(results)
        return myfunction(url).then(function(requestResult){
             return results.concat(requestResult)
        });
    });
}, Q.resolve([]));

您也可以将其变成一个助手:

1
2
3
4
5
6
7
8
9
10
11
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库有一个帮助程序来简化这种事情。

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


这是我为Q设置受限制的map函数的动力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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数组(要使用的网址),
  • worker(必须是接受项目并返回promise的函数)
  • 并且在任何给定时间最多可以处理concurrent个项目。

它返回

  • 一个承诺和
  • 当所有工人都完成后,解决一系列已兑现的承诺。

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

在您的情况下,您可以这样使用它,例如15个并发请求:

1
2
3
4
5
6
7
8
9
10
// 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);
});

可以使用递归来解决。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
});

您可以在then()块中请求新的URL

1
2
3
4
5
6
7
myFunction(urls[0]).then(function(result) {
  myFunction(urls[1]).then(function(result) {
    myFunction(urls[2]).then(function(result) {
      ...
    });
  });
});

当然,这将是它的动态行为。解决诺言后,我将保留一个队列并从单个网址出队。然后提出另一个要求。也许有一个将URL与结果相关的哈希对象。

第二点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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) {
      ...
    });
  });
});