Подтвердить что ты не робот

Async для цикла в node.js

Я новичок в этом node.js. Я немного запутался в этом обратном вызове. В моем приложении внутри цикла for я вызываю вызов асинхронной функции, я думаю, что моя проблема в том, что до того, как я получаю ответ асинхронного вызова my for loop get looped.

Мой код:

async.forEach(Object.keys(config), function(key, next) {
        search(config[key].query, function(err, result) { // 
        console.log("fffffffffff="+ util.inspect(result))-------- >>>Getting undefined..
            if (err) return next(err) // 
            var json = JSON.stringify({
                "result": result
            });
            results[key] = {
                "result": result
            }
            console.log("rrrrrrrr="+util.inspect(results[key]))
            next() // <---- critical piece.  This is how the forEach knows to continue to the next loop.  Must be called inside search callback so that it doesn't loop prematurely.                   
        })
    },
    function(err) {
        console.log('iterating done');

         res.writeHead(200, {
        'content-type': 'application/json'
    });
    res.end(JSON.stringify(results));  
    });


}

Код функции поиска:

var matches = [];
    var qrySubString = query.substring(0, 4);
    client.query("select * from xxxxxxxxx where level4 ILIKE '%" + query + "%'", function(err, row1, fields) {
        for (var id in row1.rows) {                
            var match, name;                
            if (query == row1.rows[id].level4) {
                match = true;
                name = row1.rows[id].level4;
            }
            else {
                match = false;
                name = query;
            }
            matches.push({
                "id": id,
                "name": row1.rows[id].level4,
                "score": 100,
                "match": match,
                "type": [{
                    "id": "/people/presidents",
                    "name": "US President"
                }]
            })
        }           
        callback(matches);
    })

Я хочу выполнить цикл for после успешного выполнения 1 функции поиска, я думаю, что мне нужно использовать async для цикла. Пожалуйста, помогите мне решить эту проблему. Спасибо заранее.

4b9b3361

Ответ 1

Я уменьшил ваш пример кода до следующих строк, чтобы было легче понять объяснение концепции.

var results = [];
var config = JSON.parse(queries);
for (var key in config) {
    var query = config[key].query;
    search(query, function(result) {
        results.push(result);
    });
}
res.writeHead( ... );
res.end(results);

Проблема с предыдущим кодом заключается в том, что функция search является асинхронной, поэтому, когда цикл закончился, ни одна из функций обратного вызова не была вызвана. Следовательно, список results пуст.

Чтобы устранить проблему, вы должны поместить код после цикла в функцию обратного вызова.

    search(query, function(result) {
        results.push(result);
        // Put res.writeHead( ... ) and res.end(results) here
    });

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

Чтобы получить список всех ключей, используйте Object.keys. Затем, чтобы перебирать этот список, я использую .forEach (вы также можете использовать for (var i = 0, key = keys[i]; i < keys.length; ++i) { .. }, но это может привести к проблемам, см. Закрытие JavaScript внутри циклов - простой практический пример).

Вот полный пример:

var results = [];
var config = JSON.parse(queries);
var onComplete = function() {
    res.writeHead( ... );
    res.end(results);
};
var keys = Object.keys(config);
var tasksToGo = keys.length;
if (tasksToGo === 0) {
   onComplete();
} else {
    // There is at least one element, so the callback will be called.
    keys.forEach(function(key) {
        var query = config[key].query;
        search(query, function(result) {
            results.push(result);
            if (--tasksToGo === 0) {
                // No tasks left, good to go
                onComplete();
            }
        });
    });
}

Примечание. Асинхронный код в предыдущем примере выполняется параллельно. Если функции нужно вызывать в определенном порядке, то вы можете использовать рекурсию для получения желаемого эффекта:

var results = [];
var config = JSON.parse(queries);
var keys = Object.keys(config);
(function next(index) {
    if (index === keys.length) { // No items left
        res.writeHead( ... );
        res.end(results);
        return;
    }
    var key = keys[index];
    var query = config[key].query;
    search(query, function(result) {
        results.push(result);
        next(index + 1);
    });
})(0);

То, что я показал, это концепции, вы могли бы использовать один из многих (сторонних) модулей NodeJS в своей реализации, например async.

Ответ 2

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

Я большой поклонник https://github.com/caolan/async, и он мне помогает. В основном с этим вы получите что-то вроде:

var async = require('async')
async.eachSeries(Object.keys(config), function (key, next){ 
  search(config[key].query, function(err, result) { // <----- I added an err here
    if (err) return next(err)  // <---- don't keep going if there was an error

    var json = JSON.stringify({
      "result": result
    });
    results[key] = {
      "result": result
    }
    next()    /* <---- critical piece.  This is how the forEach knows to continue to
                       the next loop.  Must be called inside search callback so that
                       it doesn't loop prematurely.*/
  })
}, function(err) {
  console.log('iterating done');
}); 

Я надеюсь, что это поможет!

Ответ 3

Мне нравится использовать рекурсивный шаблон для этого сценария. Например, что-то вроде этого:

// If config is an array of queries
var config = JSON.parse(queries.querrryArray);   

// Array of results
var results;

processQueries(config);

function processQueries(queries) {
    var searchQuery;

    if (queries.length == 0) {
        // All queries complete
        res.writeHead(200, {'content-type': 'application/json'});
        res.end(JSON.stringify({results: results}));
        return;
    }

    searchQuery = queries.pop();

    search(searchQuery, function(result) {
        results.push(JSON.stringify({result: result}); 
        processQueries();            
    });

}

processQueries - это рекурсивная функция, которая вытаскивает элемент запроса из массива запросов для обработки. Затем функция обратного вызова вызывает запрос processQueries снова, когда запрос завершен. processQueries знает, что конец, когда не осталось запросов.

Проще всего это сделать с помощью массивов, но он может быть изменен для работы с объектным ключом/значениями, которые я себе представляю.

Ответ 4

Node.js введен async await в 7.6, поэтому это делает Javascript более красивым.

var results = [];
var config = JSON.parse(queries);
for (var key in config) {
  var query = config[key].query;
  results.push(await search(query));
}
res.writeHead( ... );
res.end(results);

Для этого для работы search fucntion должен вернуть promise или должен быть async function

Если он не возвращает promise, вы можете помочь ему вернуть promise

function asyncSearch(query) {
  return new Promise((resolve, reject) => {
   search(query,(result)=>{
    resolve(result);
   })
  })
}

Затем заменим эту строку await search(query); на await asyncSearch(query);