asynchronous请求在node.js中的ForEach中

我是新的node.js(和request.js)。 我想从不同path的特定url获取网站正文(在http://www.example.com/path1下方的示例中, http://www.example.com/path2等) )并使用键/值映射(下面的siteData [path])将这些数据logging在一个对象中。

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 } 

我的问题是:

  • if语句(index ===长度)看起来不像是确定asynchronous请求是否完成的正确方法。 我该如何正确检查请求是否完成?
  • 当我执行上面的代码时,我得到一个错误(node) warning: possible EventEmitter memory leak detected. 11 unpipe listeners added. Use emitter.setMaxListeners() to increase limit. (node) warning: possible EventEmitter memory leak detected. 11 unpipe listeners added. Use emitter.setMaxListeners() to increase limit. 我尝试链接request(url, function(...){}).setMaxListeners(100); 但是这并没有奏效。

谢谢你的帮助!

看起来Promise是在这里完成工作的正确工具。 我们将创build一个新的Promise对象来解决工作完成时的问题,而不是callback。 我们可以用“ .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 }); 

我们有很多asynchronous任务要做,所以只有一个承诺是行不通的。 一种select是将它们串联在一起,如下所示:

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

但是失去了一些asynchronous的好处 – 我们可以同时完成所有这些任务。

 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也会为你工作)。 它需要一个promise数组,并返回一个promise,当所有数组的promise完成执行时,这个promise将被parsing。 (如果他们中的任何一个错误,立即拒绝)。 .then函数将被赋予一个数组来表示每个promise都parsing的值。

 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!'); }); 

更清洁和更容易的error handling。

由于nodejs中request方法的asynchronous性质,你不能直接知道他们的响应并且实时地执行。 您必须等待callback才能到达,然后只能调用下一个request方法。

在这种情况下,您正在调用forEach循环中的所有request方法,这意味着它们将逐个调用,而不必等待以前的响应。

我build议如下使用美妙的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); } ); 

希望这可以帮助。

根据我的经验,在处理请求模块时,不能只使用forEach或任何types的循环,因为它asynchronous执行,并以EventEmitter内存泄漏结束。

我解决这个问题的方法是使用recursion函数。 你可以参考下面的代码:

 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 

我同意上面的解决scheme,承诺可能是在这种情况下走的路; 不过,您也可以使用callback来实现同样的效果。

lodash库提供了跟踪已完成多less个asynchronous调用的便捷方法。

 '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); }; 

使用这个方法,done函数将跟踪有多less请求已经完成,并且在每个url被加载后调用最后的callback。