Javascript 逐行处理文件,并保持输出中的行顺序
因此,我正在编写一个NodeJS路由,用户上传一个文件(缓冲区),逐行处理(需要调用respapi来处理每一行),然后将结果输出到另一个缓冲区,作为下载文件发送给用户 这是路线代码: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
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)