2015-11-25 7 views
1

Я новичок в node.js (и request.js). Я хотел бы вернуть тело веб-сайта из определенного URL-адреса с разными путями (в примере ниже http://www.example.com/path1, http://www.example.com/path2 и т. Д.) И зарегистрировать эти данные в объекте с отображением ключа/значения (siteData [путь] ниже).Асинхронный запрос внутри ForEach в node.js

var request = require('request'), 
    paths = ['path1','path2','path3'], 
    siteData = {}, 
    pathLength = paths.length, 
    pathIndex = 0; 

paths.forEach((path) => { 
    var url="http://www.example.com/"+path; 
    request(url, function(error, response, html){ 
     if(!error){ 
      siteData[path] = response.body; 
      pathIndex++; 
      if(pathIndex===pathLength){ 
       someFunction(siteData); 
      } 
     } 
}); 

function someFunction(data){ 
    //manipulate data 
} 

Мои вопросы:

  • Условный оператор (индекс === длина) не похож на правильный путь, чтобы определить, асинхронные запросы закончены. Как правильно проверить, завершены ли запросы?
  • Когда я выполняю код выше, я получаю сообщение об ошибке (node) warning: possible EventEmitter memory leak detected. 11 unpipe listeners added. Use emitter.setMaxListeners() to increase limit. Я пробовал цепочки request(url, function(...){}).setMaxListeners(100);, но это не сработало.

Благодарим за помощь!

ответ

7

Похоже, что обещание - это правильный инструмент, чтобы выполнить эту работу здесь. Вместо обратного вызова мы создадим новый объект Promise, который будет разрешен при выполнении задания. Можно сказать, «как только вы закончите, сделать некоторые вещи» с .then оператора:

var rp = require('request-promise'); 

rp('http://www.google.com') 
    .then((htmlString) => { 
    // Process html... 
    }); 

(если что-то пойдет не так, обещание отвергающего и идет прямо к .catch)

someFunctionThatErrors('Yikes!') 
    .then((data) => { 
    // won't be called 
    }) 
.catch((err) => { 
    // Will be called, we handle the error here 
}); 

У нас есть много задач async, поэтому просто одно обещание не сработает. Одним из вариантов является строка их все вместе в серии, например, так:

rp('http://www.google.com') 
    .then((htmlString) => rp('http://someOtherUrl.com')) 
    .then((otherHtmlString) => { 
    // and so forth... 

Но что теряет часть удивительным из асинхронном - мы можем сделать все эти задачи в параллельно.

var myRequests = []; 
myRequests.push(rp('http://www.google.com').then(processStuff).catch(handleErr)); 
myRequests.push(rp('http://someOtherUrl.com').then(processStuff).catch(handleErr)); 

... мальчик делает это выглядит уродливым. Есть лучший способ со всем этим - Promise.all() (Вы используете функции стрелок, поэтому я предполагаю, что родной Promise будет работать и для вас). Он принимает множество обещаний и возвращает обещание, которое разрешается, когда все обещаний массива закончено. (Если какая-либо из них ошибка, она сразу же отвергает).Функция .then будет иметь массив , представляющий ценность каждого обещанного решения.

var myRequests = []; 
myRequests.push(rp('http://www.google.com')); 
myRequests.push(rp('http://someOtherUrl.com')); 
Promise.all(myRequests) 
    .then((arrayOfHtml) => { 
    // arrayOfHtml[0] is the results from google, 
    // arrayOfHtml[1] is the results from someOtherUrl 
    // ...etc 
    arrayOfHtml.forEach(processStuff); 
    }) 
    .catch(/* handle error */); 

Тем не менее, мы должны вручную вызвать .push для каждой ссылки мы хотим попасть. Этого не будет! Давайте тянуть ловкий трюк с использованием Array.prototype.map, который будет перебирать через наш массив, манипулируя каждое значение в свою очередь, и возвращает новый массив, состоящий из новых значений:

var arrayOfPromises = paths.map((path) => rp(`http://www.example.com/${path}`)); 
Promise.all(arrayOfPromises) 
    .then((arrayOfHtml) => arrayOfHtml.forEach(processStuff)) 
    .catch(function (err) { console.log('agh!'); }); 

Намного чище и проще обработки ошибок.

1

Из-за асинхронного метода request в nodejs вы не можете напрямую знать свои ответы и действовать в реальном времени. Вы дождались возврата обратного вызова, а затем вы можете вызвать следующий метод request.

Здесь в этом случае вы вызываете все методы request в цикле forEach, что означает, что они вызываются один за другим, не дожидаясь предыдущих ответов.

Я хотел бы предложить использовать замечательную async библиотеки для этой цели, как показано ниже -

var async = require('aysnc'); 
var request = require('request'), 
paths = ['path1','path2','path3'], 
siteData = {}, 
pathLength = paths.length, 
pathIndex = 0, 
count = 0; 

async.whilst(
    function() { return count < pathLength; }, 
    function (callback) { 
    // do your request call here 
    var path = paths[pathLength]; 
    var url="http://www.example.com/"+path; 
    request(url, function(error, response, html){ 
    if(!error){ 
     siteData[path] = response.body; 
     // call another request method 
     count++; 
     callback(); 
    } 
    }); 
}, 
function (err) { 
    // all the request calls are finished or an error occurred 
    // manipulate data here 
    someFunction(siteData); 
} 
); 

Надеется, что это помогает.

0

Я согласен с вышеизложенным решением, обещающим, вероятно, путь в этом случае; однако вы можете использовать обратные вызовы для достижения того же самого.

Библиотека lodash предлагает удобные способы отслеживания того, сколько асинхронных вызовов было завершено.

'use strict'; 

var _ = require('lodash'); 
var path = require('path'); 

var paths = ['a', 'b', 'c']; 
var base = 'www.example.com'; 

var done = _.after(paths.length, completeAfterDone); 

_.forEach(paths, function(part) { 
    var url = path.join(base, part); 
    asynchFunction(url, function() { 
     done(); 
    }); 
}); 

function completeAfterDone() { 
    console.log('Process Complete'); 
} 

function asynchFunction(input, cb) { 
    setTimeout(function() { 
     console.log(input); 
     cb(); 
    }, Math.random() * 5000); 
}; 

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

1

По моему опыту вы не можете просто использовать цикл forEach или какой-либо петли при работе с модулем запроса, так как он выполняется асинхронно и заканчивается утечкой памяти EventEmitter.

Способ, которым я решаю это, используя рекурсивную функцию. Вы можете ознакомиться с приведенным ниже кодом:

var request = require('request'), 
    paths = ['path1','path2','path3'], 
    siteData = {}; 

function requestSiteData(paths) { 
    if (paths.length) { 
     var path = paths.shift(); 
     var url = "http://www.example.com/" + path; 

     request(url, function(error, response, html) { 
      if(!error) { 
       siteData[path] = response.body; 
      } //add else block if want to terminate when error occur 

      //continue to process data even if error occur 
      requestSiteData(paths); //call the same function 
     }); 
    } else { 
     someFunction(siteData); //all paths are requested 
    } 
} 

function someFunction(data){ 
    //manipulate data 
} 

requestSiteData(paths); //start requesting data