npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ricokahler/lazy

v2.0.4

Published

A small, useful set of methods for lazy iteration of iterables

Downloads

5

Readme

@ricokahler/lazy · codecov github status checks bundlejs.com semantic-release

A small (~900B gzip*), useful set of methods for lazy iteration of iterables.



Why this lazy lib?

  1. Small size (~900B gzipped)
  2. Modern for...of-only implementations. (Read the source here)
  3. Only ships useful* methods
  4. Support for async iterators

*Useful is a keyword here because this library only ships with iterable methods that can take advantage of chained lazy iteration.

What that boils down to is shipping methods that never have to exhaust the iterable in order to yield the next item.

An example of this would be a reverse() method. If this lib implemented a reverse() method, it would have to buffer the items received from the iterable into an array to be able to yield the items in reverse.

Since we have to buffer the items into an array anyway, there's not all that much difference between:

Lazy.from(iterable).reverse(); // 🛑 not implemented!

and

Lazy.from(iterable).to(Array).reverse();

This constraint has some mental model benefits too. If you find yourself frequently needing to convert your iterable to an array, then there's a good chance lazy evaluation is not giving you much benefit.

Do I even need a lazy lib?

In order to take full advantage of the short-circuiting nature of call-by-need/lazy-evaluation, your Lazy expression should either:

  1. terminate in short-circuiting method
  2. take (via take or takeWhile) a subset of the iterable

Installation

npm i @ricokahler/lazy

Usage

Both ESM and CJS are supported in this package.

// esm
import Lazy from '@ricokahler/lazy';
// common-js
const Lazy = require('@ricokahler/lazy');

You can then:

  1. chain the methods like arrays:
const result = Lazy.from([1, 2, 3])
  .map((x) => x + 1)
  .filter((x) => x >= 3)
  .first();

console.log(result); // 3
  1. Or use the static method counterparts:
let iterable = Lazy.from([1, 2, 3]);
iterable = Lazy.map(iterable, (x) => x + 1);
iterable = Lazy.filter(iterable, (x) => x >= 3);
const result = Lazy.first(iterable);

console.log(result); // 3

Method Reference

Conversion methods

Chainable methods

Short-circuiting, terminating methods

Conversion methods

Lazy.from | 🔝

Takes in any iterable and returns it wrapped in a Lazy with chainable Lazy methods.

// static method only
Lazy.from<T>(iterable: Iterable<T>): Lazy<T>

Note: this is equivalent to calling new Lazy(iterable)

to | 🔝

Writes the iterable into another data structure. Accepts an object with a from method that accepts an iterable (e.g. Array.from) or a constructor that accepts an iterable.

Note: if used with an async iterable, it will await and buffer all items into an array first.

The implementation is as follows:

function to(iterable, constructorOrFromable) {
  // switches behavior for async iterators
  if (typeof iterable[Symbol.asyncIterator] === 'function') {
    return toAsync(iterable, constructorOrFromable);
  }

  return toSync(iterable, constructorOrFromable);
}

async function toAsync(iterable, constructorOrFromable) {
  // Async iterators will be buffered into an array first
  const items = [];
  for await (const item of iterable) {
    items.push(item);
  }

  return toSync(items, constructorOrFromable);
}

function toSync(iterable, constructorOrFromable) {
  if (typeof constructorOrFromable.from === 'function') {
    return constructorOrFromable.from(iterable);
  }
  return new constructorOrFromable(iterable);
}
Lazy<T>.to<TInstance>(fromable: {from: (iterable: Iterable<T>) => TInstance}): TInstance
Lazy<T>.to<TInstance>(constructor: {new (iterable: Iterable<T>) => TInstance}): TInstance

Chainable methods

map | 🔝

Takes in an iterable and returns an iterable generator that yields the result of the callback function on each item from the input iterable.

Lazy<T>.map<R>(mapper: (t: T) => R): Lazy<R>

filter | 🔝

Takes in an iterable and returns an iterable generator that yields the accepted elements of the given callback function.

Lazy<T>.filter<R extends T>(accept: (t: T) => t is R): Lazy<R>
Lazy<T>.filter<R extends T>(accept: (t: T) => t is R): Lazy<R>

scan | 🔝

Takes in an iterable, a reducer, and an initial accumulator value and returns another iterable that yields every intermediate accumulator created in the reducer for each item in the input iterable.

Useful for encapsulating state over time.

Note: the initial accumulator value is required.

Lazy<T>.scan<TAcc>(reducer: (acc: TAcc, t: T) => TAcc, initialAcc: TAcc): Lazy<TAcc>

flat | 🔝

Returns a new iterable with all sub-iterable items yielded into it recursively up to the specified depth.

Lazy<T>.flat<TDepth extends number>(depth?: TDepth): Lazy<Flattened<T, TDepth>>

flatMap | 🔝

Calls the result of the given callback function on each item of the parent iterable. Then, yields the result of each into a flatted iterable. This is identical to a map followed by flat with depth 1.

Lazy<T>.flatMap<R>(mapper: (value: T) => R | Iterable<R>): Lazy<R>

take | 🔝

Yields the first n items of the given iterable and stops further iteration.

const result = Lazy.from([1, 2, 3, 4, 5]).take(3).to(Array);

console.log(result); // [1, 2, 3]
Lazy<T>.take(n: number): Lazy<T>

takeWhile | 🔝

Yields while the callback function accepts the current item from the given iterable. Iteration finishes as soon as an item is rejected by the callback.

const result = Lazy.from([1, 2, 3, 4, 5, 0, 1])
  .takeWhile((n) => n <= 2)
  .to(Array);
console.log(result); // [1, 2]
Lazy<T>.takeWhile<R extends T>(accept: (t: T) => t is R): Lazy<R>
Lazy<T>.takeWhile(accept: (t: T) => unknown): Lazy<T>

skip | 🔝

Skips over the first n items of the given iterable then yields the rest.

const result = Lazy.from([1, 2, 3, 4, 5]).skip(2).to(Array);
console.log(result); // [3, 4, 5]
Lazy<T>.skip(n: number): Lazy<T>

skipWhile | 🔝

Skips over the items while the given callback accepts the current item from the given iterable, then yields the rest.

const result = Lazy.from([1, 2, 3, 4, 5, 0, 1])
  .skipWhile((n) => n <= 2)
  .to(Array);

console.log(result); // [3, 4, 5, 0, 1]
Lazy<T>.skip(n: number): Lazy<T>

Short-circuiting, terminating methods

includes | 🔝

Determines whether an iterable includes a certain value using === comparison. Short-circuits iteration once the value is found.

Lazy<T>.includes(t: T): boolean
Lazy<T>.includes(t: T): Promise<boolean> // if async iterable is provided

first | 🔝

Returns the first item of an iterable or undefined if the iterable is done/exhausted.

Lazy<T>.first(): T | undefined
Lazy<T>.first(): Promise<T | undefined> // if async iterable is provided

find | 🔝

Returns the first item accepted by the given callback. Short-circuits iteration once an item is found.

Lazy<T>.find<R extends T>(accept: (t: T) => t is R): R | undefined
Lazy<T>.find(accept: (t: T) => unknown): T | undefined
Lazy<T>.find(accept: (t: T) => unknown): Promise<T | undefined> // if async iterable is provided

some | 🔝

Returns true if at least one item accepted by the given callback. Short-circuits iteration once an item is accepted.

Lazy<T>.some(accept: (t: T) => unknown): boolean
Lazy<T>.some(accept: (t: T) => unknown): Promise<boolean> // if async iterable is provided

every | 🔝

Returns true only if all items are accepted by the given callback. Short-circuits iteration once an item is rejected.

Lazy<T>.every(accept: (t: T) => unknown): boolean
Lazy<T>.every(accept: (t: T) => unknown): Promise<boolean> // if async iterable is provided