.each и обратные вызовы

Я использую модули узла request и 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 прошла цикл по всем элементам?

Могу ли я сделать это с помощью async?

Спасибо


person Spearfisher    schedule 12.05.2014    source источник


Ответы (3)


.each функция cheerio является синхронной (см. исходный код). Итак, пока вы не делаете ничего асинхронного в обратном вызове (как в случае с вопросом), вам нечего делать: на следующей строке цикл будет завершен.


Если вы выполняете вызов асинхронных функций в цикле, самым чистым и простым решением является использование библиотеки 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
});
person Denys Séguret    schedule 12.05.2014
comment
Я использую результаты для выполнения асинхронного запроса к другому API. Это моя проблема :( - person Spearfisher; 12.05.2014
comment
Тогда нам нужно больше узнать об этих запросах. Они должны быть синхронными? - person Denys Séguret; 12.05.2014
comment
@Spearfisher Исправьте пример в вопросе, действительно похоже, что вы выполняете только синхронные операции в обратном вызове .each - person Denys Séguret; 12.05.2014

Вы можете использовать модуль async, чтобы легко обрабатывать такие виды асинхронных задач.

В частности, async.each или async.eachLimit, если вам нужен параллелизм> 1 или async. eachSeries, если вы хотите просмотреть элементы в массиве по одному.

person mscdex    schedule 12.05.2014

Я должен признать, что мне не удалось заставить работать решение 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
        }
    });
});

В этом конкретном примере кода кажется, что вы могли бы выполнять свои асинхронные вызовы внутри функций сопоставления, но вы понимаете суть ...

Карта: 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

person Katya S    schedule 26.06.2015