Skip to content

Instantly share code, notes, and snippets.

@aziis98
Last active January 31, 2022 23:34
Show Gist options
  • Select an option

  • Save aziis98/a4d18ba49f7490fa91a8f78d33b0e95e to your computer and use it in GitHub Desktop.

Select an option

Save aziis98/a4d18ba49f7490fa91a8f78d33b0e95e to your computer and use it in GitHub Desktop.

Tail

Run a function for every line in a file following its output. This is just a more modern version of the code found at https://stackoverflow.com/questions/11225001/reading-a-file-in-real-time-using-node-js and only uses open from fs/promises.

Usage

Just pass a callback that consumes the lines of the followed file.

import tail from './tail.js';

tail('test.txt', line => {
    console.log(`reader 1: "${line}"`);
});

const done2 = tail('test.txt', { sleepTimeout: 500 }, line => {
    console.log(`reader 2: "${line}"`);
});

// Second reader terminates after 5s
setTimeout(() => {
    console.log('Stopping 2');
    done2();
}, 5000);

(this example can be tryied out with the example-writer.js file below)

import { open } from 'fs/promises';
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
function _tail(path, options, onLine) {
const chunkSize = options.chunkSize ?? 512;
const sleepTimeout = options.sleepTimeout ?? 2000;
let done = false;
// The main reader loop
(async function () {
const fd = await open(path, 'r');
const chunkBuffer = Buffer.alloc(chunkSize);
let readBytes = 0;
let lineBuffer = '';
while (!done) {
// Get stats to check file size
const { size: fileSize } = await fd.stat();
if (fileSize <= readBytes) {
// Ahead of writer, sleeping for a bit...
await sleep(sleepTimeout);
} else {
// Read from last read position in file
const r = await fd.read(chunkBuffer, 0, chunkSize, readBytes);
readBytes += r.bytesRead;
lineBuffer += chunkBuffer.toString().slice(0, r.bytesRead);
// Pass all read completed lines to consumer
while (lineBuffer.includes('\n')) {
const line = lineBuffer.slice(0, lineBuffer.indexOf('\n'));
onLine(line);
lineBuffer = lineBuffer.slice(line.length + 1);
}
}
}
})();
return () => done = true;
}
export default function tail() {
if (arguments.length === 2) {
const [path, onLine] = arguments;
return _tail(path, {}, onLine);
}
if (arguments.length === 3) {
const [path, options, onLine] = arguments;
return _tail(path, options, onLine);
}
throw 'Illegal number of arguments';
}
import { createWriteStream } from 'fs';
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
let l = 1;
async function onOpen() {
console.log('Stream opened');
while (true) {
console.log('Writing...');
stream.write(`This is a line ${l++}\n`);
await sleep(500);
}
}
const stream = createWriteStream('test.txt', { flags: 'a' });
stream.on('open', onOpen);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment