0

I built a simple asynchronous implementation of the readlines module built into nodejs, which is simply a wrapper around the event-based module itself. The code is below;

const readline = require('readline');

module.exports = {
  createInterface: args => {
    let self = {
      interface: readline.createInterface(args),
      readLine: () => new Promise((succ, fail) => {
        if (self.interface === null) {
          succ(null);
        } else {
          self.interface.once('line', succ);
        }
      }),
      hasLine: () => self.interface !== null
    };
    self.interface.on('close', () => {
      self.interface = null;
    });
    return self;
  }
}

Ideally, I would use it like so, in code like this;

const readline = require("./async-readline");

let filename = "bar.txt";

let linereader = readline.createInterface({
  input: fs.createReadStream(filename)
});

let lines = 0;
while (linereader.hasLine()) {
  let line = await linereader.readLine();
  lines++;
  console.log(lines);
}

console.log("Finished");

However, i've observed some erratic and unexpected behavior with this async wrapper. For one, it fails to recognize when the file ends, and simply hangs once it reaches the last line, never printing "Finished". And on top of that, when the input file is large, say a couple thousand lines, it's always off by a few lines and doesn't successfully read the full file before halting. in a 2000+ line file it could be off by as many as 20-40 lines. If I throw a print statement into the .on('close' listener, I see that it does trigger; however, the program still doesn't recognize that it should no longer have lines to read.

Maurdekye
  • 3,597
  • 5
  • 26
  • 43
  • Looks like the `line` event fires synchronously multiple times on rare occurrences, so that the next event happens before your promise is resolved and the next `once` handler is installed. – Bergi Jun 24 '19 at 16:39
  • Regarding the last line: you are calling `hasLine()` and `readLine()` before the `close` event fires, while `self.interface` still is not `null`. It goes to wait for the next `line` event, which never happens. – Bergi Jun 24 '19 at 16:40
  • You should instead have a look at asynchronous iterators and `for await … of`. I guess there are already working solutions of how to convert an event emitter like `readline` into such an async iterator. – Bergi Jun 24 '19 at 16:41
  • Do you have a solution to propose for any of this? It seems to be a fundamental flaw with the `readlines` module, or asynchronous code in general. – Maurdekye Jun 24 '19 at 16:41
  • No, the fundamental flaw is trying to make a promise interface from events like you did. The `readlines` module is supposed to be used with a callback style, and your attempt at writing an adapter was not sufficient. – Bergi Jun 24 '19 at 16:43
  • You might be interested in [this solution](https://stackoverflow.com/a/48551466/1048572) as well (although `for await of` is surely supreme) – Bergi Jun 30 '19 at 13:57

1 Answers1

2

It seems that in nodejs v11.7, the readline interface was given async iterator functionality and can simply be looped through with a for await ... of loop;

const rl = readline.createInterface({
  input: fs.createReadStream(filename);
});

for await (const line of rl) {
  console.log(line)
}

How to get synchronous readline, or "simulate" it using async, in nodejs?

Maurdekye
  • 3,597
  • 5
  • 26
  • 43