暂停Node.js中的可读流

时间:2017-01-21 18:27:22

标签: node.js stream

我使用csv-to-json,一个整洁的库来处理CSV文件。

我有一个用例,我需要处理一个大的(> 2百万行)CSV并将其插入数据库。

为了在不遇到内存问题的情况下执行此操作,我打算将CSV作为流处理,每10000行暂停流,在我的数据库中插入行,然后恢复流。

由于某些原因,我似乎无法pause信息流。

以下面的代码为例:

const rs = fs.createReadStream("./foo.csv");
rs.pause();

let count = 0;

csv()
.fromStream(rs)
.on("json", (json) => {
  count++;
  console.log(count);
})
.on("done", () => {
  cb(null, count);
})
.on("error", (err) => {
  cb(err);
})

count被记录200次(这是我的CSV中有多少行) - 我希望它不会记录任何内容,因为在将流传递给{{1}之前流已暂停}

3 个答案:

答案 0 :(得分:2)

以下是图书馆创建者建议的解决方案,在此Issue中进行了跟踪:

var tmpArr=[];
rs.pipe(csv({},{objectMode:true})).pipe(new Writable({
  write: function(json, encoding,callback){
    tmpArr.push(json);
    if (tmpArr.length===10000){
      myDb.save(tmpArr,function(){
        tmpArr=[];
        callback();
      })
    }else{
      callback();
    }
  } ,
  objectMode:true
}))
.on('finish',function(){
  if (tmpArr.length>0){
    myDb.save(tmpArr,function(){
      tmpArr=[];
    })
  }
})

我实际上设法通过像这样的删除来模仿暂停,但它并不理想:

let count = 0;
var csvParser=csv()
.fromStream(rs)
.on("json", (json) => {
  rows.push(json);
  if (rows.length % 1000 === 0) {
    rs.unpipe();
    // clear `rows` right after `unpipe`
    const entries = rows;
    rows = [];
    this._insertEntries(db, entries, ()=> {
      rs.pipe(csvParser);
    });
  }
})

答案 1 :(得分:1)

除非修改csv2json库,否则无法执行此操作。

这是您应该首先阅读的链接
https://nodejs.org/dist/latest-v6.x/docs/api/stream.html#stream_three_states

当你执行rs.pause()时,流处于暂停模式。事实上,即使你不这样做,可读流也会以暂停模式启动。

在3种情况下,流进入resume

  • 要么是.on('data')事件监听器,要么
  • 附加了.pipe()方法或
  • readable.resume()被明确调用。

在您的情况下,fromStream()方法将pipe方法附加到您的可读流中,从而恢复流。

参考代码:
https://github.com/Keyang/node-csvtojson/blob/master/libs/core/Converter.js#L378

Converter.prototype.fromStream=function(readStream,cb){
  if (cb && typeof cb ==="function"){
    this.wrapCallback(cb);
  }
  process.nextTick(function(){
    readStream.pipe(this);
  }.bind(this))
  return this;
}

答案 2 :(得分:1)

我利用了csvtojson也有fromString(...)方法的事实,并将其用于下面。

  1. 使用line-by-line包读取固定行数,即10000,并将它们存储在数组中。
  2. 使用lr.pause()暂停逐行阅读器。
  3. 在索引0处插入标题行(如果您的csv文件具有标题行,然后使用简单的条件语句忽略逐行阅读器返回的第一行)。
  4. 使用EOL字符加入所有行,这将为您提供该CSV文件的10000行的字符串表示。
  5. 使用csvtojson' s .fromString(...)将块的字符串表示转换为json对象并将其插入db。
  6. 通过lr.resume()重新开始播放,然后重复直到逐行阅读器发出'end'个事件。
  7. 这里有完整的代码

    const CSVToJSON = require("csvtojson");
    const LineByLineReader = require("line-by-line");
    const { EOL } = require("os");
    
    const BLOCK_LIMIT = 10000;
    
    let lines = [];
    let isFirstLineProcessed = false;
    
    const lr = new LineByLineReader("./foo.csv");
    
    lr
    .on("line", (line) => {
    
        // remove this if statement if your CSV does not contain headers line
        if (!isFirstLineProcessed) {
            isFirstLineProcessed = true;
            return;
        }
    
        lines.push(line);
    
        if (lines.length === BLOCK_LIMIT) {
            lr.pause();
    
            // insert headers string ("field1, field2, ...") at index 0;
            lines.splice(0, 0, headers);
    
            // join all lines using newline operator ("\n") to form a valid csv string
            const csvBlockString = lines.join(EOL);
            const entries = [];
    
            lines = [];      
    
            csv()
                .fromString(csvBlockString)
                .on("json", (json) => {
                    entries.push(json);
                })
                .on("done", () => {
                    this._insertEntries(db, entries, ()=> {
                        lr.resume();
                   });
                });
        }
    })
    .on("end", () => {
        console.log("done");
    });