异步并行的HTTP请求

18

我在使用 Caolan Async 和 NPM request 模块加载大量 URL 的应用程序中遇到了控制流问题。

我的问题是,HTTP 响应会在将函数添加到队列时立即开始。理想情况下,我希望先建立队列,只有在队列开始时才开始发出 HTTP 请求。否则,在队列开始之前回调函数就开始触发,导致队列过早结束。

var request = require('request') // https://www.npmjs.com/package/request
    , async = require('async'); // https://www.npmjs.com/package/async

var myLoaderQueue = []; // passed to async.parallel
var myUrls = ['http://...', 'http://...', 'http://...'] // 1000+ urls here

for(var i = 0; i < myUrls.length; i++){
    myLoaderQueue.push(function(callback){

        // Async http request
        request(myUrls[i], function(error, response, html) {

            // Some processing is happening here before the callback is invoked
            callback(error, html);
        });
    });
}

// The loader queue has been made, now start to process the queue
async.parallel(queue, function(err, results){
    // Done
});

有没有更好的方法来解决这个问题?

3个回答

34

使用 for 循环结合异步调用在ES5中存在问题,可能会导致意外的结果(在您的情况下,会获取错误的URL)。

相反,考虑使用async.map()

async.map(myUrls, function(url, callback) {
  request(url, function(error, response, html) {
    // Some processing is happening here before the callback is invoked
    callback(error, html);
  });
}, function(err, results) {
  ...
});

如果你需要获取1000个以上的URL,也许可以考虑使用async.mapLimit()


完美运行。谢谢。 - ChrisRich
在ES6中,这会更少出现问题吗? - xShirase
除了使用似乎已不推荐的“请求(request)”方法,还有其他的方法吗? - Jonny
1
@Jonny,这取决于您的具体要求,但我猜测任何基于Promise的HTTP库(如gothttp.minaxios)结合Promise.allSettled()(或基于Promise的速率限制器/队列)都可以。 - robertklep
我最终使用了 Q (Q.allSettled()) 来处理并行操作,而使用 https 来进行实际请求。 - Jonny
显示剩余2条评论

9

如果你想要开始使用BluebirdBabel来利用promisesES7async/await,你可以按照以下步骤操作:

let Promise = require('bluebird');
let request = Promise.promisify(require('request'));

let myUrls = ['http://...', 'http://...', 'http://...'] // 1000+ urls here

async function load() {
  try {
    // map myUrls array into array of request promises
    // wait until all request promises in the array resolve
    let results = await Promise.all(myUrls.map(request));
    // don't know if Babel await supports syntax below
    // let results = await* myUrls.map(request));
    // print array of results or use forEach 
    // to process / collect them in any other way
    console.log(results)
  } catch (e) {
    console.log(e);
  }
}

1
无法使用此答案,因为我的项目基于ES5,不想重写整个程序。 - ChrisRich
2
...但是,发表 Node.js ES7 的做法值得赞扬! - ChrisRich
4
你也可以使用 await* myUrls.map(request),而不是使用 Promise.all。这将实现相同的功能。 - Michael
@MichaelVayvala,这种语法在Babel中可能仍然有效,但它已经不再是规范的一部分了。链接 - SimpleJ

0

我相信你遇到了不同的错误。当你的排队函数正在评估时,i已经被重新定义,这可能导致它看起来像你错过了第一个URL。在排队函数时尝试使用一些闭包。

var request = require('request') // https://www.npmjs.com/package/request
    , async = require('async'); // https://www.npmjs.com/package/async

var myLoaderQueue = []; // passed to async.parallel
var myUrls = ['http://...', 'http://...', 'http://...'] // 1000+ urls here

for(var i = 0; i < myUrls.length; i++){
    (function(URLIndex){
       myLoaderQueue.push(function(callback){

           // Async http request
           request(myUrls[URLIndex], function(error, response, html) {

               // Some processing is happening here before the callback is invoked
               callback(error, html);
           });
       });
    })(i);
}

// The loader queue has been made, now start to process the queue
async.parallel(queue, function(err, results){
    // Done
});

我的答案可能是正确的,但robertklep的更好。但愿他早5分钟发帖或者我慢5分钟。 - trex005
是的,这个问题很常见 :-) 顺便说一下,myUrls[i] 应该改为 myUrls[URLIndex] - robertklep

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接