1

我是 node.js(和 request.js)的新手。我想从具有不同路径的特定 url 中获取网站的正文(在下面的示例中http://www.example.com/path1http://www.example.com/path2等。 ) 并将此数据记录在具有键/值映射的对象中(下面的 siteData[path])。

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 === length) 看起来不是确定异步请求是否完成的正确方法。我应该如何正确检查请求是否已完成?
  • 当我执行上面的代码时,我得到一个错误(node) warning: possible EventEmitter memory leak detected. 11 unpipe listeners added. Use emitter.setMaxListeners() to increase limit.,我尝试链接request(url, function(...){}).setMaxListeners(100);,但没有奏效。

谢谢你的帮助!

4

4 回答 4

10

看起来 Promises 是在这里完成工作的正确工具。我们将创建一个新对象,而不是回调,该Promise对象将在工作完成时解析。我们可以对操作员说“一旦你完成了,再做一些事情” .then

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

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

(如果出现任何问题,promise会拒绝并直接转到.catch

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

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

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也适用于您)。它接受一个 Promise 数组并返回一个 Promise,该 Promise 在数组的所有Promise 都完成执行后解析。(如果其中任何一个出错,它会立即拒绝)。该.then函数将获得一个数组,该数组表示每个 Promise 解析为的值。

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

更清洁和更容易的错误处理。

于 2015-11-25T06:18:34.380 回答
2

根据我的经验,在处理请求模块时不能只使用 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
于 2015-12-02T08:44:23.603 回答
1

由于requestnodejs 中方法的异步特性,您无法直接知道它们的响应并实时采取行动。您必须等待回调到达,然后才能调用下一个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);
 }
);

希望这可以帮助。

于 2015-11-25T05:44:19.250 回答
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);
};

使用此方法,done 函数将跟踪有多少请求已完成,并在每个 url 加载后调用最终回调。

于 2015-12-01T02:44:17.597 回答