节点脚本内存不足(循环内循环)

时间:2017-07-26 21:06:29

标签: javascript performance

我有一个非常基本的脚本,我一直在逐步添加功能 - 几乎没有优化。

它应该通过X brands并为每个(每个都有一个唯一的端点)运行API调用,然后将结果保存为CSV。

CSV部分工作得很好,尽管速度有点慢。 (200KB文件约20秒)。

然而,当我把它全部包裹在brands的循环中时,它现在超时了。默认情况下它会运行2分钟然后给我一个错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

我学会了追加--max_old_space_size=4000000给它多余的记忆(它也死于2GB)。在4GB我没有得到错误,但我得到Windows内存泄漏警告,所以很明显我的脚本有问题。我觉得它在循环中循环,但不确定。

var http = require("https");
var qs = require("querystring");
var csvWriter = require('csv-write-stream');
var fs = require('fs');
var moment = require('moment');
let key = '';
var brands = ['REDACTED1','REDACTED2','REDACTED3','REDACTED4','REDACTED5','REDACTED6','REDACTED7','REDACTED8']
var dataRequest = function(token){
  var date = new Date()-1; //Make it yesterday
  var formattedDate = moment(date).format('YYYYMMDD');
  var yesterdayDashes = moment(date).format('YYYY-MM-DD');
  for (var k=0;k<=brands.length;k++){

    var headers = [];
    var csvBody = [];
    var csvContent = "data:text/csv;charset=utf-8,";
    var options = {
      "method": "GET",
      "hostname": "REDACTED",
      "port": "443",
      "path": "REDACTED/"+brands[k]+"/"+yesterdayDashes+"?REDACTED&access_token="+token,
      "headers": {
        "authentication": "Bearer "+token,
        "content-type": "application/json",
        "cache-control": "no-cache"
      }
    }

    var req = http.request(options, function (res) {
      var chunks = [];

      res.on("data", function (chunk) {
        chunks.push(chunk);
      });

      res.on("end", function () {
        var body = Buffer.concat(chunks);
      var object = JSON.parse(body);
      var writer = csvWriter({
        headers: ["REDACTED1", "REDACTED2", "REDACTED3", "REDACTED4", "REDACTED5", "REDACTED6", "REDACTED7", "REDACTED8", "REDACTED9"]
      })
      writer.pipe(fs.createWriteStream(brands[k] +'_' +formattedDate +'_DEMOGRAPHIC.csv'))
      for (var i=0;i<object.length; i++){
        writer.write([
          object[i].field1.REDACTED1, 
          moment(object[i].optin.REDACTED2).format('YYYYMMDD HHMMSS'), 
          object[i].field2.REDACTED1, 
          object[i].field2.REDACTED2, 
          object[i].field2.REDACTED3, 
          object[i].field2.REDACTED4, 
          object[i].field2.REDACTED5, 
          object[i].field3.REDACTED6, 
          object[i].field3.REDACTED7
        ])
      }
      writer.end()
      });
    });
    req.end();
  }
}

var authToken = function(){

  var form = qs.stringify({
    grant_type: 'client_credentials',
    client_credentials: 'client_id:client_secret',
    client_id: 'cdg-trusted-client',
    client_secret: 'REDACTED'
  })
  var options = {
    "method": "POST",
    "hostname": "REDACTED3",
    "port": null,
    "path": "REDACTED3",
    "headers": {
      "Content-Type": "application/x-www-form-urlencoded",
      "cache-control": "no-cache"
    }
  };

  var req = http.request(options, function (res) {
    var chunks = [];

    res.on("data", function (chunk) {
      chunks.push(chunk);
    });

    res.on("end", function () {
      var body = Buffer.concat(chunks);
      var json = JSON.parse(body);
      key = json['access_token'];
    });
  });

  req.write(form);
  req.end();
  dataRequest(key);
}
authToken();

我删除了敏感信息,但所有逻辑仍然存在。这是一个我快速拼凑的脚本,但老实说,我真的没有看到它需要这么多内存的任何理由。我想也许它是无限循环但直接在节点内测试每个循环我没有问题。

流程开始获取持有者令牌,然后将其传递给函数以提取数据。

当我在讨论把它放在CodeReview中时,这段代码根本不是技术

更新 修改第一个for循环现在立即输出unidentified CSV并且不完全是其他任何内容。但是console.log(brands[k])正在输出相应的文件。

更新2

我的JS调试版本在任何地方放置console.log(),我注意到一旦我低于http.request init,品牌[k]突然变得不明确。我认为这可能是由于没有传递给函数吗?

更新3

我的undefined问题是由分号丢失引起的,早期结束了for循环。我已经纠正了它,但现在我又遇到了Max Stack Trace问题。

我的问题上下文现在似乎是“我如何使for不能运行异步?”

2 个答案:

答案 0 :(得分:3)

这是你的问题: for (var k=0;k=brands.length;k++){ 您使用了作为赋值运算符的运算符=,并将品牌长度放入变量k而不是<运算符,以检查k是否仍小于brands.length

在循环中测试的表达式是数字8(品牌数组的长度),它始终保持为8,因此您将进入无限循环。

请注意,对于布尔表达式,任何不同于0的值都表示值true,而0表示false。

这是关于无限循环问题,同时请注意,将代码传递给dataRequest函数时,代码还有另一个问题。

问题是你试图在req.end()之后立即传递它;发送请求。

此时响应仍未存在,res.on("end", ...的回调函数仍未执行。 基本上你将undefined传递给dataRequest函数,导致你的第二个错误。只需将dataRequest的调用移至end回调内,就像这样:

res.on("end", function () {
   var body = Buffer.concat(chunks);
   var json = JSON.parse(body);
   key = json['access_token'];
   dataRequest(key);
});

答案 1 :(得分:0)

var http = require("https");
var qs = require("querystring");
var csvWriter = require('csv-write-stream');
var fs = require('fs');
var moment = require('moment');
let key = '';
var brands = ['REDACTED1','REDACTED2','REDACTED3','REDACTED4','REDACTED5','REDACTED6','REDACTED7','REDACTED8']
var dataRequest = function(token, client){
  var date = new Date()-1; //Make it yesterday
  var formattedDate = moment(date).format('YYYYMMDD');
  var yesterdayDashes = moment(date).format('YYYY-MM-DD');
    var headers = [];
    var csvBody = [];
    var csvContent = "data:text/csv;charset=utf-8,";
    var options = {
      "method": "GET",
      "hostname": "REDACTED",
      "port": "443",
      "path": "REDACTED/"+client+"/"+yesterdayDashes+"?REDACTED&access_token="+token,
      "headers": {
        "authentication": "Bearer "+token,
        "content-type": "application/json",
        "cache-control": "no-cache"
      }
    }; // <--- This was the culprit

    var req = http.request(options, function (res) {
      var chunks = [];

      res.on("data", function (chunk) {
        chunks.push(chunk);
      });

      res.on("end", function () {
        var body = Buffer.concat(chunks);
      var object = JSON.parse(body);
      var writer = csvWriter({
        headers: ["REDACTED1", "REDACTED2", "REDACTED3", "REDACTED4", "REDACTED5", "REDACTED6", "REDACTED7", "REDACTED8", "REDACTED9"]
      })
      writer.pipe(fs.createWriteStream(client +'_' +formattedDate +'_DEMOGRAPHIC.csv'))
      for (var i=0;i<object.length; i++){
        writer.write([
          object[i].field1.REDACTED1, 
          moment(object[i].optin.REDACTED2).format('YYYYMMDD HHMMSS'), 
          object[i].field2.REDACTED1, 
          object[i].field2.REDACTED2, 
          object[i].field2.REDACTED3, 
          object[i].field2.REDACTED4, 
          object[i].field2.REDACTED5, 
          object[i].field3.REDACTED6, 
          object[i].field3.REDACTED7
        ])
      }
      writer.end()
      });
    });
    req.end();
  }
}

var authToken = function(){

  var form = qs.stringify({
    grant_type: 'client_credentials',
    client_credentials: 'client_id:client_secret',
    client_id: 'cdg-trusted-client',
    client_secret: 'REDACTED'
  })
  var options = {
    "method": "POST",
    "hostname": "REDACTED3",
    "port": null,
    "path": "REDACTED3",
    "headers": {
      "Content-Type": "application/x-www-form-urlencoded",
      "cache-control": "no-cache"
    }
  };

  var req = http.request(options, function (res) {
    var chunks = [];

    res.on("data", function (chunk) {
      chunks.push(chunk);
    });

    res.on("end", function () {
      var body = Buffer.concat(chunks);
      var json = JSON.parse(body);
      key = json['access_token'];
      for (var k=0;k<=brands.length;k++){
         var client = brands[k];
         dataRequest(key, client);

    });
  });

  req.write(form);
  req.end();

}
authToken();

最后,如上所述,我使用k = x时出现问题,一旦我将其修改为k < x,我就已对代码进行了多次更改,从而导致上述更新。除k = x之外,我上面的分号不合适,这打破了for

我还选择按brand发起一个电话,而不是在品牌内发起。该剧本现在运作良好。

我已经添加了一个辅助答案,不会夸大问题并最终显示整个工作代码。