Get in Line, Deno!

I’ve updated my Deno Tail Lines module to improve performance. See my original blog post for a quick intro. Reading a file backwards is trickier than it sounds!

The Deno standard library has a new TextLineStream. This is a useful addition. However, If you only want the last ten lines, for example, you have to skip through the entire file:

import {TextLineStream} from 'https://deno.land/std/streams/mod.ts';
let lines: string[] = [];
const file = await Deno.open('example.log’);
const lines = file.readable
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(new TextLineStream());
for await (const line of lines) {
  lines.push(line);
}
lines = lines.slice(-10);

Don’t do this! It’s very, very, slow when you have thousands of lines. It has to read the entire file into memory too.

That’s why I made Tail Lines. It’s many times faster and more efficient only reading the necessary bytes. My first implementation required two passes. One to read the line feed offsets and a second to go back and read text lines. I’ve now refactored the whole thing from scratch to use a single pass and it’s even faster.

The API remains the same:

import {tailLines} from 'https://deno.land/x/tail_lines/mod.ts';
const file = await Deno.open('example.log');
for await (const line of tailLines(file, 10)) {
  console.log(line);
}

This example will output the last ten lines. For a log file that would be reverse-chronological order.

The tailLines function is a small wrapper around a new TailLineStream class I’ve coded under the hood.

import {TailLineStream} from 'https://deno.land/x/tail_lines/mod.ts';
const file = await Deno.open('example.log');
const stream = new TailLineStream(file);
const textDecoder = new TextDecoder();
for await (const line of stream) {
  console.log(textDecoder.decode(line));
}

It’s a readable stream so you can use pipes:

const stream = new TailLineStream(file)
  .pipeThrough(new TextDecoderStream());
for await (const line of stream) {
  console.log(line);
}

However, if you just need text decoding use tailLines it’s slightly faster.


Web streams are neat. I made an XML stream recently.

Speaking of lines, I’ve published another project called Carriageway.

Run async and promise-returning functions with limited concurrency and optional rate limiting.

More on that soon!

Buy me a coffee! Support me on Ko-fi