node.js - 检测到 EventEmitter 内存泄漏 : Proper way to pass CSV data to multiple modules?

标签 node.js npm gulp

我正在尝试以自己的方式使用不同的 npm 模块,而在我刚刚执行之前已经创建了 gulpfiles。 npm 模块 penthouse加载网页并确定该页面的首屏 CSS。我正在尝试采用该模块并将其与网站爬虫一起使用,这样我就可以获得所有页面的折叠 CSS 上方,并将该 CSS 存储在表中。

所以本质上我是:

  • 抓取网站以获取所有网址
  • 从每个网址捕获页面 ID
  • 将页面及其 ID 存储在 CSV 中
  • 加载 CSV 并将每个 URL 传递到 penthouse
  • 获取顶层公寓输出并将其存储在表中

所以直到最后两步我都很好。当我读取 CSV 时,收到错误检测到可能的 EventEmitter 内存泄漏。添加了 11 个退出监听器。使用emitter.setMaxListeners()来增加限制

堆栈跟踪点 here在第 134 行。阅读完该错误后,这是有道理的,因为我看到添加了一堆事件监听器,但我没有看到 penthouse 真正执行和关闭事件监听器。

它按预期独立工作得很好(针对单个页面运行 penthouse 然后退出)。但是,当我执行下面的代码尝试循环遍历 csv 中的所有 URL 时,它会两次吐出内存泄漏错误,然后挂起。以下脚本中的 console.log 语句均未执行。

但是,我将 console.log 添加到 penthouse index.js 文件的末尾,并且它被执行多次(其中添加事件监听器),但它永远不会超时或退出。

所以很明显我没有正确集成它,但不确定如何继续。强制它一次读取 CSV 中的一行,处理 URL,然后获取输出并将其存储在数据库中,然后再转到下一行,最好的方法是什么?

const fs = require('fs');
var csv = require('fast-csv');
var penthouse = require('penthouse'),
    path = require('path');

 var readUrlCsv = function() {
  var stream = fs.createReadStream("/home/vagrant/urls.csv");

  var csvStream = csv()
      //returns single line from CSV
      .on("data", function(data) {

        // data[0]: table id, data[1]: page type, data[2]: url
        penthouse({
            url : data[2],
            css : './dist/styles/main.css'

        }, function(err, criticalCss) {
            if (err) {
              console.log(err);
            }
            console.log('do we ever get here?'); //answer is no

            if (data[1] === 'post') {

              wp.posts().id( data[0] ).post({
                  inline_css: criticalCss
              }).then(function( response ) {
                console.log('saved to db');
              });
            } else {
              wp.pages().id( data[0] ).page({
                  inline_css: criticalCss
              }).then(function( response ) {
                  console.log('saved to db');
              });
            }
        });
      })
      .on("end", function(){
           console.log("done");
      });

  return stream.pipe(csvStream);
};

更新

将我的方法更改为如下所示,以便它首先处理所有行,但仍然引发相同的错误。将“done”写入控制台,并立即吐出两次内存警告。

var readUrlCsv = function() {
  var stream = fs.createReadStream("/home/vagrant/urls.csv");
  var urls = [];
  var csvStream = csv()
      .on("data", function(data) {
        // data[0]: table id, data[1]: page type, data[2]: url
        urls.push(data);        
      })
      .on("end", function(){
           console.log("done");
           buildCriticalCss(urls);
      });

  return stream.pipe(csvStream);
};

var buildCriticalCss = function(urls) {
  //console.log(urls);
  urls.forEach(function(data, idx) {
    //console.log(data);
    penthouse({
            url : data[2],
            css : './dist/styles/main.css',
            // OPTIONAL params
            width : 1300,   // viewport width
            height : 900,   // viewport height
            timeout: 30000, // ms; abort critical css generation after this timeout
            strict: false, // set to true to throw on css errors (will run faster if no errors)
            maxEmbeddedBase64Length: 1000 // charaters; strip out inline base64 encoded resources larger than this
        }, function(err, criticalCss) {
            if (err) {
              console.log(err);
            }
            console.log('do we ever finish one?');
            if (data[1] === 'post') {
              console.log('saving post ' + data[0]);
              wp.posts().id( data[0] ).post({
                  inline_css: criticalCss
              }).then(function( response ) {
                console.log('saved post to db');
              });
            } else {
              console.log('saving page ' + data[0]);
              wp.pages().id( data[0] ).page({
                  inline_css: criticalCss
              }).then(function( response ) {
                  console.log('saved page to db');
              });
            }
        });
    });
};

更新2

我采用了简单的方法来控制生成的并发进程的数量。

var readUrlCsv = function() {
  var stream = fs.createReadStream("/home/vagrant/urls.csv");
  var urls = [];
  var csvStream = csv()
      .on("data", function(data) {
        // data[0]: table id, data[1]: page type, data[2]: url
        urls.push(data);        
      })
      .on("end", function(){
           console.log("done");
           //console.log(urls);
           buildCriticalCss(urls);
      });

  return stream.pipe(csvStream);
};


function buildCriticalCss(data) {
    var row = data.shift();
    console.log(row);
    penthouse({
        url : row[2],
        css : './dist/styles/main.css',
        // OPTIONAL params
        width : 1300,   // viewport width
        height : 900,   // viewport height
        timeout: 30000, // ms; abort critical css generation after this timeout
        strict: false, // set to true to throw on css errors (will run faster if no errors)
        maxEmbeddedBase64Length: 1000 // charaters; strip out inline base64 encoded resources larger than this
    }, function(err, criticalCss) {
      if (err) {
        console.log('err');
      }
      // handle your criticalCSS
      console.log('finished');
      console.log(row[2]);
      // now start next job, if we have more urls
      if (data.length !== 0) {
        buildCriticalCss(data);
      }
    });
}

最佳答案

您看到的错误消息是 Node 的event默认打印到控制台的错误消息。如果为 EventEmitter 实例定义的事件监听器数量超过允许数量,则使用库。它表明存在实际的内存泄漏。相反,显示它是为了确保您了解泄漏的可能性

您可以通过检查 event.EventEmitter 源代码第 20 行看到这一点。和 244 .

要阻止 EventEmitter 显示此消息,并且由于 penthouse 不公开其特定的 EventEmitter,您需要使用以下方法将默认允许的事件发射器监听器设置为大于其默认值 10:

var EventEmitter=require('event').EventEmitter;

EventEmitter.defaultMaxListeners=20;

请注意,根据 Node 的文档 EventEmitter.defaultMaxListeners ,这将更改 EventEmitter 的所有实例的最大监听器数量,包括在更改之前已定义的监听器。

或者您可以直接忽略该消息。

除了挂起代码之外,我建议将 CSV 解析的所有结果收集到一个数组中,然后与解析过程分开处理数组内容。

这将完成两件事:它将使您能够

  1. 请确保在开始处理之前整个 CSV 文件都是有效的,并且
  2. 在处理每个元素时检测调试消息,这将使您更深入地了解数组中每个元素的处理方式。

更新

如下所述,根据您正在处理的 URL 数量,您可能会超出 Node 并行处理所有请求的能力。

一种简单的继续方法是使用事件来编码处理,以便按顺序处理您的 URL,如下所示:

var assert=require('assert'),
    event=require('events'),
    fs=require('fs'),
    csv=require('fast-csv');
    penthouse=require('penthouse');

var emitter=new events.EventEmitter();

/** Container for URL records read from CSV file.
 * 
 * @type {Array}
 */
var urls=[];

/** Reads urls from file and triggers processing
 *
 *  @emits processUrl
 */
var readUrlCsv = function() {

  var stream = fs.createReadStream("/home/vagrant/urls.csv");

  stream.on('error',function(e){ // always handle errors!!
    console.error('failed to createReadStream: %s',e);
    process.exit(-1);
  });

  var csvStream = csv()
    .on("data", function(data) {
      // data[0]: table id, data[1]: page type, data[2]: url
      urls.push(data);        
    })
    .on("end", function(){
       console.log("done reading csv");
       //console.log(urls);
       emitter.emit('processUrl'); // start processing URLs
    })
    .on('error',function(e){
      console.error('failed to parse CSV: %s',e);
      process.exit(-1);
    });

  // no return required since we don't do anything with the result
  stream.pipe(csvStream);
};

/** Event handler to process a single URL
 *
 * @emits processUrl
 */
var onProcessUrl=function(){

  // always check your assumptions
  assert(Array.isArray(urls),'urls must be an array');

  var urlRecord=urls.shift();

  if(urlRecord){

    assert(Array.isArray(urlRecord),'urlRecord must be an array');

    assert(urlRecord.length>2,'urlRecord must have at least three elements');

    penthouse(
      { 
        // ... 
      }, 
      function(e,criticalCss){
        if(e){
          console.error('failed to process record %s: %s',urlRecord,e);
          return; // IMPORTANT! do not drop through to rest of func!
        }

        // do what you need with the result here

        if(urls.length===0){ // ok, we're done
          console.log('completed processing URLs');
          return;
        }

        emitter.emit('processUrl'); 
      }
    );
  }
}

/**
  * processUrl event - triggers processing of next URL
  *
  * @event processUrl
  */
emitter.on('processUrl',onProcessUrl); // assign handler

// start everything going...
readUrlCsv();

在这里使用事件而不是您的解决方案的好处是缺乏递归,这很容易压垮您的堆栈。

提示:您可以使用事件来处理通常由 Promise 或 async 等模块解决的所有程序流问题。

由于事件是 Node 的核心(“事件循环”),因此它确实是解决此类问题的最佳、最有效的方法。

它既优雅又“Node 方式”!

这是一个gist说明了该技术,不依赖于流或 penthouse,其输出为:

url: url1
RESULT: RESULT FOR url1
url: url2
RESULT: RESULT FOR url2
url: url3
RESULT: RESULT FOR url3
completed processing URLs

关于node.js - 检测到 EventEmitter 内存泄漏 : Proper way to pass CSV data to multiple modules?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36105605/

相关文章:

git - Gulp-git clone 在特定的文件夹位置。克隆到 'undefined' 文件夹

javascript - Gulp gulp-less 包含组件子文件夹

javascript - gulp.watch 进入无限循环

javascript - Node : what is require modules best practices?

javascript - VSCode 如何确保扩展的所有 Node 依赖项均已安装?

node.js - 访问循环依赖中模块导出的不存在属性 'padLevels'

docker - Dockerfile 中的开发依赖项或用于生产和测试的单独 Dockerfile

node.js - AND/OR 用于 Loopback 的 ACLS 中的角色

node.js - Gulp-connect 显示错误 'listen EADDRINUSE' 8080

css - 使用 gulp-sass npm 模块编译 scss 文件