rl[Symbol.asyncIterator]()

Returns an async iterator so a readline interface can be consumed with for await...of.

Signature
rl[Symbol.asyncIterator]()
  • Added in Node.js v11.4.0, v10.16.0

Return value

AsyncIteratorAn async iterator yielding each line of the input stream as a string, with the line terminator removed.

Errors

Does not throw on its own; errors emitted by the input stream are not forwarded to the loop and must be handled on the stream.

Changelog

  • PHP v11.14.0, v10.17.0Symbol.asyncIterator support is no longer experimental.

You rarely call this method by name — for await (const line of rl) calls it for you. Each iteration yields one line, and the loop body is awaited before the next line is produced, so backpressure is handled without touching rl.pause().

When to reach for it

  • Any per-line processing that involves await: a database write, a fetch, a hash.
  • Code that wants to break after finding what it needs — implementing head, or scanning for the first match.
  • Wrapping line reading in an async generator you can compose or test.

What trips people up

Errors on the input stream are not forwarded into the loop. A missing file or a mid-read I/O failure will surface as an unhandled 'error' event on the stream, not as a rejection from for await. Attach an 'error' listener to the read stream, or pipe it through stream.pipeline.

Leaving the loop with break, return or throw calls rl.close() for you, but the underlying stream is only released, not destroyed synchronously — the 'close' event arrives on a later tick.

The iterator is single-use and throughput is lower than the 'line' event, which still wins on very large files.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Insert each line of an NDJSON export into a database, one awaited write at a time
  • Read only the first N lines of a huge file and stop
  • Compute a checksum over a file while parsing it line by line
  • Wrap a file in an async generator that yields parsed records

Frequently asked questions

How do I read a file line by line with for await in Node.js?

Create an interface with readline.createInterface({ input: fs.createReadStream(path), crlfDelay: Infinity }) and write for await (const line of rl). Each iteration yields one line as a string and the loop body is awaited before the next line is read.

Is for await...of slower than the 'line' event?

Yes — the Node documentation notes the async iterator does not match the 'line' event on throughput, because each line goes through promise machinery. Use the event for raw scanning of huge files and the iterator whenever the per-line work is async.

Does breaking out of a for await loop over readline close the file?

Breaking, returning or throwing calls rl.close(), which relinquishes the input stream. The 'close' event fires on a later tick, so await it if you need to know the interface has finished.

More examples

Awaiting work per line without pause/resume

Each insert is awaited before the next line is read, so the database sees one write at a time no matter how large the file is. The equivalent 'line' listener would fire for every line up front and open thousands of concurrent inserts.

Example · javascript
import fs from 'node:fs';
import readline from 'node:readline';

const input = fs.createReadStream('users.ndjson');
input.on('error', (err) => { throw err; });

const rl = readline.createInterface({ input, crlfDelay: Infinity });

let imported = 0;
let skipped = 0;

for await (const line of rl) {
  if (!line.trim()) continue;
  try {
    await db.users.insert(JSON.parse(line));
    imported++;
  } catch {
    skipped++;
  }
}

console.log(`imported ${imported}, skipped ${skipped}`);

A reusable async generator of parsed records

Breaking out of the outer loop returns into the generator, which ends the inner for await and closes the interface, so the file handle is released even though the file was only partly read.

Example · javascript
import fs from 'node:fs';
import readline from 'node:readline';

async function* records(path) {
  const input = fs.createReadStream(path);
  const rl = readline.createInterface({ input, crlfDelay: Infinity });
  let lineNo = 0;
  for await (const line of rl) {
    lineNo++;
    if (!line.trim() || line.startsWith('#')) continue;
    yield { lineNo, fields: line.split('\t') };
  }
}

for await (const rec of records('data.tsv')) {
  if (rec.fields.length !== 3) {
    console.log(`bad row at line ${rec.lineNo}`);
    break;
  }
}

Reading the first lines of a stream

Only the chunks needed for five lines are read from disk; break closes the interface and the read stream is released instead of running to the end of a multi-gigabyte file.

Example · javascript
import fs from 'node:fs';
import readline from 'node:readline';

async function head(path, n) {
  const rl = readline.createInterface({
    input: fs.createReadStream(path),
    crlfDelay: Infinity,
  });
  const out = [];
  for await (const line of rl) {
    out.push(line);
    if (out.length === n) break;
  }
  return out;
}

console.log((await head('server.log', 5)).join('\n'));

Discussion

  • Be the first to comment on this lesson.