Javascript 逐行处理文件,并保持输出中的行顺序

Javascript 逐行处理文件,并保持输出中的行顺序,javascript,node.js,Javascript,Node.js,因此,我正在编写一个NodeJS路由,用户上传一个文件(缓冲区),逐行处理(需要调用respapi来处理每一行),然后将结果输出到另一个缓冲区,作为下载文件发送给用户 这是路线代码: app.post('/tokenizeFile', isLoggedIn, upload.single('file'), function(req, res){ var file = req.file; //File Validations if (!validat

因此,我正在编写一个NodeJS路由,用户上传一个文件(缓冲区),逐行处理(需要调用respapi来处理每一行),然后将结果输出到另一个缓冲区,作为下载文件发送给用户

这是路线代码:

app.post('/tokenizeFile', isLoggedIn, upload.single('file'), function(req, res){
        var file = req.file;

        //File Validations
        if (!validator.validateFile(file)) res.redirect('/?err=invalidFile');

        //Process file
        tokenizer.tokenizeFile(file, req).then((data)=>{
            //res.setHeader('Content-Length', stat.size);
            res.setHeader('Content-Type', 'text/plain');
            res.setHeader('Content-Disposition', 'attachment; filename=tokenized.txt');
            res.write(data, 'binary');
            res.end();
        }).catch((err)=>{
            res.redirect('/?err='+err);
        });

    });
这是tokenizer.tokenizeFile代码:

tokenizeFile: function(file, req){

        actionLogger.info(`Request to tokenize ${file.originalname} received. Made by: ${req.user.displayName}`);

        return new Promise(function(resolve, reject){
            var fileProcessPromise = Promise.resolve();

            var lineReader = require('readline').createInterface({
                input: require('streamifier').createReadStream(file.buffer)
            });

            var output = "";

            lineReader.on('line', function (line) {
                //Tokenize each line

                if (!validate.validateLine(line)) return reject(`Invalid line [${line}].`);
                fileProcessPromise = Tokenize(line)
                .then((data)=>{
                    output += data + "\\r\\n";
                })
                .catch((err)=>{
                    reject(`API didn\'t respond.`);
                });
            });

            lineReader.on('close', () => {
                fileProcessPromise.then(()=>resolve(output));
            });

        });

    }
Tokenize函数返回一个承诺,因为它是对RESTful API的HTTP请求

问题是,我需要输出文件保持相同的顺序,对于上面的代码,它的顺序取决于Tokenize函数解析的速度

关于如何实现这一点,有什么想法吗?

1)标记化文件代码:

tokenizeFile: (file, req) => {

  actionLogger.info(`Request to tokenize ${file.originalname} received. Made by: ${req.user.displayName}`);

  return new Promise((resolve, reject) => {

    const lines = [], responses = [];

    const lineReader = require('readline').createInterface({
      input: require('streamifier').createReadStream(file.buffer)
    });

    // 1. read all lines to array
    lineReader.on('line', line => {
      if(!validate.validateLine(line)) {
        return reject(`Invalid line [${line}].`);
      }
      lines.push(line);
    });


    lineReader.on('close', async () => {
      // 2. process every line sequentially
      try {
        for(const line of lines) {
          const response = await Tokenize(line);
          responses.push(response);
        }
        resolve(responses.join("\n"));
      }
      .catch(error => {
        console.log(error);
        reject("API didn't respond");
      });
    });

  });
}
2) 以及请求部分:

app.post(
  '/tokenizeFile', 
  isLoggedIn, 
  upload.single('file'), 
  async (req, res) => {
    try {
      const file = req.file;

      if (!validator.validateFile(file)) {
        throw new Error('invalidFile');
      }

      const data = await tokenizer.tokenizeFile(file, req);

      res.setHeader('Content-Type', 'text/plain');
      res.setHeader('Content-Disposition', 'attachment; filename=tokenized.txt');
      res.write(data, 'binary');
      res.end();
    })
    .catch(error => {
      res.redirect('/?err='+error);
    });
  });

下面是一个结合使用Promise API和async/await的解决方案:

tokenizeFile: function(file, req) {

    return new Promise((resolve, reject) => {
        actionLogger.info(`Request to tokenize ${file.originalname} received. Made by: ${req.user.displayName}`);
        var lineReader = require('readline').createInterface({
            input: require('streamifier').createReadStream(file.buffer)
        });
        linePromises = [];
        var validationFailed = false;
        lineReader.on('line', line => {
            if (!validate.validateLine(line)) {
                //stop the lineReader if possible
                reject(`Invalid line [${line}].`);
                validationFailed = true;
            }
            if (!validationFailed) {
                linePromises.push(Tokenize(line));
            }
        });
        lineReader.on('close',async () => {
                var outputStrings = [];
                for (var linePromise of linePromises) {
                    var data = await linePromise;
                    outputStrings.push(data);
                }
                var output = outputStrings.join("");
                resolve(output);
        });
    });    
}
它分两个阶段工作。。。首先,它向标记器发出所有请求承诺,并将这些承诺推送到一个数组中(与原始行的顺序相同)


当lineReader完成(
'close'
)时,我们有一个飞行中的承诺数组,我们在其上循环,依次等待每个承诺,并将承诺的结果推送到一个数组中。完成后,我们只需将数组连接成一个大字符串并解析外部承诺。

使用流读取文件而不将其缓冲到磁盘

然后使用行流拆分为行 然后使用一些并行变换 然后将结果写入响应

如果stream模块中没有实现stream.pause()和stream.resume on drain,则可能需要添加stream.pause()和stream.resume on drain。动态处理大型内容

也许可以用 将大块数据写入流。因此,通过压缩或其他传输任务处理大数据块可能比处理小数据块更容易

也许可以用

//npm安装-保存快速读线转换泵多部分读取流并行转换
var multipart=require('multipart-read-stream')
var泵=需要(‘泵’)
const ReadlineTransform=require('readline-transform');
var ParallelTransform=require('parallel-transform');
var express=require('express')
var app=express()
app.get(“/”,函数(req,res,next){
res.writeHead(200,{'content-type':'text/html'});
res.end(
''+
“
”+ “
”+ ''+ '' ); }) app.post(“/upload”),功能(请求、回复){ var multipartStream=multipart(请求头、处理程序) //res.writeHead(200,{'content-type':'text/plain'});pump(req,res);return;//调试 泵(请求、多部件流、功能(错误){ if(err){res.end('server error');return;} 决议结束() }) var=false 函数处理程序(字段名、文件名、文件名){ //例如: //console.log('从字段'+fieldname'读取文件'+filename+')) //var fileStream=fs.createWriteStream(path.join('/tmp',filename)) //泵(文件、文件流) 如果(已处理)返回; 已处理=正确; //res.writeHead(200,{'content-type':'text/plain'});pump(文件,res);return;//调试 const transform=new ReadlineTransform({skipmpty:false}); 泵(文件、转换) //res.writeHead(200,{'content-type':'text/plain'});pump(转换,res);return;//调试 var first=真; //也许: var parallel=ParallelTransform(10,{objectMode:false},函数(数据,回调){//10是并行级别 //这可能更快: //如果(第一){ //第一个=假; //回调(null,“转换:”+数据); // } //否则 //回调(空,“\r\n”+”转换:“+数据); (异步()=>{ 如果(第一){ 第一个=假; 返回“已转换:”+数据; } 其他的 { 返回“\r\n”+”转换:“+数据; } })()然后((数据)=>回调(null,数据)).catch((错误)=>回调(错误“”) }); 泵(变换,并联) //res.writeHead(200,{'content-type':'text/plain'});pump(parallel,res);return;//调试 泵(并联,res) } }).听(8080)
您能使用async/await吗?它会简化代码。async/await是语法上的糖分。然后处理,所以它不会冻结应用程序。Fede,我认为你不必把所有的事情复杂化。我认为您可以不使用逐行读取整个文件。虽然在数组中收集
Tokenize
返回的承诺的解决方案可以工作,但我仍然建议您查看转换流。使用基于流的方法,您所描述的用例可以实现更大的内存和性能效率。@num8er还没有,必须退出。几小时后就可以了。我会让你知道的。谢谢美好的如果需要,您可以删除依赖项并在
for const line of line
循环中使用async/await。@WilliamFleming oh:)我忘了使用尼斯更新!最终版本很容易理解。@WilliamFleming异步软件包很好,但我必须说,就今天而言,nodejs开发人员必须使用最新版本,以备更改。@FedeE。乐于助人。很高兴读到它。谢谢(:p.s.它可以通过将数组行分块到每个数组组10个(处理限制)项来实现,并且使用
Promise.all()
(;或者您可以使用
async.parallelLimit
(我说的是
async
模块,谷歌it)我对
linereader
包进行了一次简短的阅读,没有发现任何明显的方法来阻止它。因此,要求使用标志
validationFailed
,这样一旦我们拒绝,我们就不会再对后续回调做任何工作。
// npm install -save express readline-transform pump multipart-read-stream parallel-transform 

var multipart = require('multipart-read-stream')
var pump = require('pump')
const ReadlineTransform = require('readline-transform');
var ParallelTransform = require('parallel-transform');
var express = require('express')


var app = express()

app.get("/",function (req, res, next) {
  res.writeHead(200, {'content-type': 'text/html'});
  res.end(
    '<form action="/upload" enctype="multipart/form-data" method="post">'+
    '<input type="text" name="title"><br>'+
    '<input type="file" name="upload" multiple=""><br>'+
    '<input type="submit" value="Upload">'+
    '</form>'
  );
})


app.post("/upload",function (req, res) {


  var multipartStream = multipart(req.headers, handler)

 // res.writeHead(200, {'content-type': 'text/plain'}); pump(req, res);  return; // debug

  pump(req, multipartStream, function (err) {
    if (err) { res.end('server error'); return;}
    res.end()
  })



  var handled=false
  function handler (fieldname, file, filename) {
    // from example:
    //console.log('reading file ' + filename + ' from field ' + fieldname)
    //var fileStream = fs.createWriteStream(path.join('/tmp', filename))
    //pump(file, fileStream)

    if(handled) return;
    handled=true;

     // res.writeHead(200, {'content-type': 'text/plain'}); pump(file, res);  return; // debug

    const transform = new ReadlineTransform({ skipEmpty: false });
    pump(file, transform)
    //res.writeHead(200, {'content-type': 'text/plain'}); pump(transform, res);  return; // debug



    var first=true;
    // maybe:
    var parallel = ParallelTransform(10, {objectMode:false}, function(data, callback) { // 10 is the parallism level

            // this might be faster:
            //  if(first){
            //      first=false;
            //      callback(null, "transformed:"+data);     
            //  }
            //  else
            //      callback(null, "\r\n"+"transformed:"+data);


        (async()=>{

            if(first){
                first=false;
                return "transformed:"+data;
            }
            else
            {               
                return "\r\n"+"transformed:"+data;
            }

        })().then( (data)=>callback(null,data) ).catch(  (error)=>callback(error,"") )


    });
    pump(transform, parallel)
    //res.writeHead(200, {'content-type': 'text/plain'}); pump(parallel, res);  return; // debug




    pump(parallel, res)

  }

}).listen(8080)