Pensierinmusica
Pensierinmusica

Reputation: 6968

What is the most efficient way to read only the first line of a file in Node JS?

Imagine you have many long text files, and you need to only extract data from the first line of each one (without reading any further content). What is the best way in Node JS to do it?

Thanks!

Upvotes: 28

Views: 35994

Answers (6)

GOTO 0
GOTO 0

Reputation: 47801

Node.js >= 16

In all current versions of Node.js, readline.createInterface can be used as an async iterable, to read a file line by line - or just for the first line. This is also safe to use with empty files.

Unfortunately, the error handling logic is broken in versions of Node.js before 16, where certain file system errors may go uncaught even if the code is wrapped in a try-catch block because of the way asynchronous errors are propagated in streams. So I recommend using this method only in Node.js >= 16.

import { createReadStream } from "fs";
import { createInterface } from "readline";

async function readFirstLine(path) {
    const inputStream = createReadStream(path);
    try {
        for await (const line of createInterface(inputStream)) return line;
        return ''; // If the file is empty.
    }
    finally {
        inputStream.destroy(); // Destroy file stream.
    }
}

const firstLine = await readFirstLine("path/to/file");

Upvotes: 4

TOPKAT
TOPKAT

Reputation: 8678

I know this doesn't exactly answer the question but for those who are looking for a READABLE and simple way to do so:

const fs = require('fs').promises;

async function getFirstLine(filePath) {
    const fileContent = await fs.readFile(filePath, 'utf-8');
    return (fileContent.match(/(^.*)/) || [])[1] || '';
} 

NOTE:

  • naturaly, this will only work with text files, which I assumed you used from your description
  • this will work with empty files and will return an empty string
  • this regexp is very performant since it is simple (no OR conditions`or complex matches) and only reads the first line

Upvotes: 7

Viktor Molokostov
Viktor Molokostov

Reputation: 633

There's a built-in module almost for this case - readline. It avoids messing with chunks and so forth. The code would look like the following:

const fs = require('fs');
const readline = require('readline');

async function getFirstLine(pathToFile) {
  const readable = fs.createReadStream(pathToFile);
  const reader = readline.createInterface({ input: readable });
  const line = await new Promise((resolve) => {
    reader.on('line', (line) => {
      reader.close();
      resolve(line);
    });
  });
  readable.close();
  return line;
}

Upvotes: 20

Yin
Yin

Reputation: 643

Please try this:

https://github.com/yinrong/node-line-stream-util#get-head-lines

It unpipe the upstream once got the head lines.

Upvotes: 3

Pensierinmusica
Pensierinmusica

Reputation: 6968

I ended up adopting this solution, which seems the most performant I've seen so far:

var fs = require('fs');
var Q = require('q');

function readFirstLine (path) {
  return Q.promise(function (resolve, reject) {
    var rs = fs.createReadStream(path, {encoding: 'utf8'});
    var acc = '';
    var pos = 0;
    var index;
    rs
      .on('data', function (chunk) {
        index = chunk.indexOf('\n');
        acc += chunk;
        index !== -1 ? rs.close() : pos += chunk.length;
      })
      .on('close', function () {
        resolve(acc.slice(0, pos + index));
      })
      .on('error', function (err) {
        reject(err);
      })
  });
}

I created a npm module for convenience, named "firstline".

Thanks to @dandavis for the suggestion to use String.prototype.slice()!

Upvotes: 14

Safi
Safi

Reputation: 1132

//Here you go;

var lineReader = require('line-reader');
var async = require('async');

exports.readManyFiles = function(files) {
    async.map(files, 
        function(file, callback))
            lineReader.open(file, function(reader) {
              if (reader.hasNextLine()) {
                reader.nextLine(function(line) {
                  callback(null,line);
                });
              }
            });
        },
        function(err, allLines) {
            //do whatever you want to with the lines
        })
}

Upvotes: 2

Related Questions