I thought this would be an interesting challenge to use async I/O and as little memory as possible (buffering only what a readStream gives you on a data
event) and outputting each line as you go.
So, here's a way to do it. This is not extensively tested on very large files, but does seem to work in the test cases I tried. If the two files have a different number of lines, it will stop outputting lines when the shorter file has been read. Here's the code:
const LineReader = require('line-by-line');
const fs = require('fs');
class DualLineReader extends LineReader {
constructor(filename, outputStream) {
super(filename);
this.myBuffer = [];
this.output = outputStream;
}
setOtherReader(lr) {
this.otherLineReader = lr;
}
resumeIfEmpty() {
if (!this.myBuffer.length) {
this.resume();
}
}
// if data in both buffers, write the next line
// call the callback when OK to call writeLine again
writeLine(cb) {
// if both buffers contain at least one line, output the first line in each
if (this.myBuffer.length && this.otherLineReader.myBuffer.length) {
let ready = this.output.write(this.myBuffer.shift() + " + " + this.otherLineReader.myBuffer.shift() + '\n');
if (!ready) {
// need to wait for drain event before writing any more
this.output.once('drain', () => {
cb(true);
});
} else {
process.nextTick(() => {
cb(true);
});
}
} else {
// nothing else to write at the moment
// call the callback on next tick
process.nextTick(() => {
cb(false);
});
}
}
closeOutput(cb) {
if (!this.output.closed) {
this.output.end(cb);
}
}
// loop asynchronously until no more data in buffer to write
// call callback when done
writeAllLines(cb = function() {}) {
this.writeLine(more => {
if (more) {
this.writeAllLines();
} else {
// if either buffer is empty, start it flowing again
this.resumeIfEmpty();
this.otherLineReader.resumeIfEmpty();
cb();
}
});
}
run(cb) {
this.on('line', line => {
this.myBuffer.push(line);
this.pause();
this.writeAllLines();
});
this.on('end', () => {
this.writeAllLines(() => {
this.close();
this.otherLineReader.close();
this.closeOutput(cb);
});
});
this.on('error', (err) => {
console.log(err);
this.close();
this.otherLineReader.close();
this.closeOutput(() => {
cb(err);
});
});
}
}
let output = fs.createWriteStream("results.txt");
output.on('close', () => {
this.closed = true;
});
let lr1 = new DualLineReader("file1.txt", output);
let lr2 = new DualLineReader("file2.txt", output);
lr1.setOtherReader(lr2);
lr2.setOtherReader(lr1);
function done() {
console.log("all done");
}
lr1.run(done);
lr2.run(done);