Skip to content

Iterators

Eugene Lazutkin edited this page Jul 19, 2026 · 6 revisions

JavaScript provides a notion of iterators and helpers to deal with them:

Modern runtimes (Node 22+, current Bun, Deno, and browsers) ship native iterator helpers (Iterator.from(), .map(), .filter(), .take(), .drop(), .flatMap(), …). This module doesn't reimplement them: it bridges the iterator/iterable protocols and delegates to the native helpers when they are available. On runtimes with Iterator.from(), normalizeIterator() is the gateway to the whole native helper family: normalizeIterator(it).take(3).

iterators.js

Legend for tables

The following utilities are available:

Function Return value Description
augmentIterator(iterator) iterator Augment iterator with an iterable interface if it is not defined.
normalizeIterator(iterator) iterator Similar to augmentIterator(), but can use Iterator.from() if it is defined.
mapIterator(iterable, callbackFn) iterator Creates a lazy iterator that calls the callback function for each element and returns the result as a value.
filterIterator(iterable, callbackFn) iterator Creates a lazy iterator that emits the values for which the callback function returns truthy.

mapIterator() and filterIterator() accept an iterable or a bare iterator and always return a lazy, single-use iterable iterator — even for arrays (they never build an eager array). return() is forwarded to the source, so an early exit (e.g. break in for...of) closes the source iterator and runs its cleanup (finally blocks in generators). On modern runtimes they delegate to the native Iterator.prototype.map()/.filter().

augmentIterator() and normalizeIterator() are here to bridge the gap between the iterable protocol and the iterator protocol to connect to all JavaScript iteration mechanisms.

The iterator protocol defines a set of methods that can be used to iterate over a collection of elements. The iterable protocol defines a special method Symbol.iterator that returns an iterator. for...of and other JavaScript facilities expect iterables. The way to make an iterable out of an iterator is to define Symbol.iterator on the iterator returning this. That is what augmentIterator() does.

normalizeIterator() tries to use Iterator.from() if it is available and falls back to augmentIterator().

In most cases, augmentIterator() and normalizeIterator() are used by library implementors. On runtimes without native helpers, normalizeIterator() degrades to augmentIterator() (no helper methods) — mapIterator() and filterIterator() remain the portable transformation helpers, modelled on Iterator.prototype.map() and Iterator.prototype.filter() respectively. This module deliberately wraps no further helpers — reach for the native family through normalizeIterator() when you need take/drop/flatMap/….

Examples

import {normalizeIterator, filterIterator, mapIterator} from 'meta-toolkit/iterators.js';

class Range {
  constructor(from, to) {
    this.from = from;
    this.to = to;
  }

  [Symbol.iterator]() {
    let index = this.from;
    return normalizeIterator({
      next: () => {
        if (index >= this.to) return {done: true};
        return {value: index++, done: false};
      }
    });
  }

  static from(from, to) {
    return new Range(from, to);
  }
}

for (const x of Range.from(1, 5)) {
  console.log(x); // 1, 2, 3, 4
}

const range = new Range(1, 5),
  odds = filterIterator(range, x => x % 2),
  squared = mapIterator(odds, x => x * x);

for (const x of squared) {
  console.log(x); // 1, 9
}

Exports

All functions are exported by their names. There is no default export.

Clone this wiki locally