6

我正在使用请求和cheerio 节点模块来创建从网站中获取一些数据。我想得到一个项目列表,一旦这个列表完成,调用一个异步函数:

request('http://myurl', function(req,res,data){
    var $ = cheerio.load(data);
    var List = [];

    $('.myItems').each(function(i, element){
        console.log( typeof $(this).text() )
        List.push($(this).text());
    });

   for (var i=0; i <  List.length; i++){
      // make an asynchronous call to a API
   }
});

我的问题是如何等待列表完成,即,我怎么知道 .each 函数已经遍历了所有项目?

我可以用异步来做到这一点吗?

谢谢

4

4 回答 4

14

Cheerio的.each功能是同步的(参见源代码)。因此,只要您在回调中不做任何异步操作(问题就是这种情况),您就无事可做:在下一行,循环将完成。


如果您确实在循环中调用异步函数,那么最简洁和最简单的解决方案是使用 Promise 库。

以顺序方式(以Bluebird为例):

var p = Promise.resolve();
$('.myItems').each(function(i, element){
    p = p.then(function(){ 
         // do something with $(this).text() and return a promise
    });
});
p.then(function(){
   // all asynchronous tasks are finished
});

如果不需要顺序请求(这里是Q的示例):

Q.allSettled($('.myItems').map(function(){
   // return a promise
})).then(function(){
   // all asynchronous tasks are finished
});
于 2014-05-12T11:54:34.563 回答
2

好吧,对于遇到此问题的其他人,只要知道您实际上并不需要使用该each方法,您可以使用该方法将它们转换为数组并在toArray循环中迭代它们for

这样,就不需要使用像 Bluebird 这样的外部库。

request('http://myurl', function(req,res,data){
    var $ = cheerio.load(data);
    var List = [];

    const items = $('.myItems').toArray();
    for(let i = 0; i < items.length; i++){
        const el = items[i];
        console.log( typeof $(el).text() )
        List.push($(el).text());
        // make an asynchronous call to a API
    }
});
于 2021-10-01T18:52:00.197 回答
1

您可以使用async模块轻松处理这些类型的异步任务。

特别是 async.eachasync.eachLimit如果您需要 concurrency > 1 或async.eachSeries如果您想遍历数组中的项目,一次一个。

于 2014-05-12T12:01:13.960 回答
1

我不得不承认我没有设法让 Denys Séguret 的解决方案正常工作(对于 .each() 循环中的异步调用)——“之后”操作仍然发生在 .each() 循环完成之前,但是我发现了一个不同的方式,我希望它可以帮助某人:

var Promises = require('bluebird');

request('http://myurl', function(req,res,data){
    var $ = cheerio.load(data);
    var List = [];

    Promises
    // Use this to iterate serially
    .each($('.myItems').get(), function(el){
        console.log( typeof $(el).text() )
        List.push($(el).text());
    })
    // Use this if order of items is not important
    .map($('.myItems').get(), function(el){
        console.log( typeof $(el).text() )
        List.push($(el).text());
    }, {concurrency:1}) // Control how many items are processed at a time
    // When all of the above are done, following code will be executed
    .then(function(){
        for (var i=0; i <  List.length; i++){
            // make an asynchronous call to a API
        }
    });
});

在这个特定的代码示例中,您似乎可以在 then 映射函数中进行异步调用,但您明白了要点......

地图:https ://github.com/petkaantonov/bluebird/blob/master/API.md#mapfunction-mapper--object-options---promise

每个:https ://github.com/petkaantonov/bluebird/blob/master/API.md#eachfunction-iterator---promise

于 2015-06-26T11:05:43.277 回答