Skip to main content
Module

x/better_iterators/mod.ts

Chainable iterators (sync and async) for TypeScript, with support for opt-in, bounded parallelism
Go to Latest
import * as betterIterators from "https://deno.land/x/better_iterators@v1.0.0/mod.ts";

This module provides [Lazy] and [LazyAsync] classes which make it easy to chain together data transformations.

The [lazy] function is the simplest way to get started.

import { lazy, range } from "./mod.ts"

// You can use any Iterable here (such as an Array, or Generator), but
// Lazy objects are themselves Iterable:
let iterable = range({to: 1000})

let results = lazy(iterable)
    .filter(it => it % 2 == 0)
    .map(it => it*it)
    .limit(10)

// No iteration has happened yet.
// This will trigger it:
for (let item of results) { console.log(item) }


// Note that iterating a Lazy(Async) will consume its items -- the operation is
// not repeatable. If you need to iterate multiple times, save your result to
// an array with `toArray()`.

let results2 = lazy([1, 2, 3, 4, 5]).map(it => `number: ${it}`)

// Consumes all values:
for (let result of results2) { console.log("first pass:", result)}

// Has no more values to yield: (may throw an exception in the future!)
for (let result of results2) { console.log("second pass:", result)}

[LazyAsync] provides a similar interface to Lazy, but performs operations asynchronously. You can create the lazy pipeline synchronously, but iterating its values requires a for-await loop or an await results.toArray()

Compare this version, which tries to do async work inside of Lazy:

import { lazy, range } from "./mod.ts"
let urls = [
    "https://www.google.com/",
    "https://www.bing.com/"
]
let lazySizes = lazy(urls)
    .map(async (url) => {
        let response = await fetch(url)
        return await response.text()
    })
    // The type is now Lazy<Promise<string>> so we're stuck in promises for
    // the rest of the lazy chain:
    .map(async (bodyPromise) => (await bodyPromise).length)
    // and so on...

// `lazySizes` also ends up as a Lazy<Promise<string>>, so we've got to 
// await all the items ourselves:
let sizes = await Promise.all(lazySizes.toArray())

// This approach might seem to work, but it has unbounded parallelism. If you
// have N URLs, .toArray() will create N promises, and the JavaScript runtime
// will start making progress on all of them simultaneously.

For a simpler API when working with async code, you can convert a Lazy to a LazyAsync, which provides a similar API, but better handles async pipelines:

import { lazy, range } from "./mod.ts"
let urls = [
    "https://www.google.com/",
    "https://www.bing.com/"
]
let lazySizes = lazy(urls)
    .toAsync()
    .map(async (url) => {
        let response = await fetch(url)
        return await response.text()
    })
    // here the type is LazyAsync<string> (not Lazy<Promise<string>>)
    // so further lazy functions are easier to work with:
    .map(it => it.length)

// The async version of toMap() does the least surprising thing and processes
// the pipeline serially by default, no unbounded parallelism:
let sizes = await lazySizes.toArray()

If you DO want parallelism, the LazyAsync.mapPar and LazyAsync.mapParUnordered methods let you explicitly opt into this behavior with bounded parallelism.

Functions

Create a Lazy(Async) from your (Async)Iterator.

Returns a Lazy which will yield consecutive numbers.

Type Aliases

Filters for matches (where boolean is true)

A function that transforms from In to Out