Home

Awesome

<div align="center"> <img src="https://github.com/TomerAberbach/lfi/blob/main/sloth.svg" alt="Sloth juggling office supplies" width="400" /> </div> <h1 align="center"> lfi </h1> <div align="center"> <a href="https://npmjs.org/package/lfi"> <img src="https://badgen.now.sh/npm/v/lfi" alt="version" /> </a> <a href="https://github.com/TomerAberbach/lfi/actions"> <img src="https://github.com/TomerAberbach/lfi/workflows/CI/badge.svg" alt="CI" /> </a> <a href="https://unpkg.com/lfi/dist/index.min.js"> <img src="https://deno.bundlejs.com/?q=lfi&badge" alt="gzip size" /> </a> <a href="https://unpkg.com/lfi/dist/index.min.js"> <img src="https://deno.bundlejs.com/?q=lfi&config={%22compression%22:{%22type%22:%22brotli%22}}&badge" alt="brotli size" /> </a> <a href="https://github.com/sponsors/TomerAberbach"> <img src="https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86" alt="Sponsor"> </a> </div> <div align="center"> A <b>l</b>azy <b>f</b>unctional <b>i</b>teration library supporting sync, async, and concurrent iteration. </div>

Features

Table of Contents

Install

$ npm i lfi

Usage

Here are some examples!

Some synchronous operations:

import {
  filter,
  map,
  pipe,
  reduce,
  toArray,
  toGrouped,
  toMap,
  toSet,
} from 'lfi'

const messySlothDiaryEntries = [
  [`Carl`, `slept`],
  [`phil`, `ate  `],
  [`phil`, ``],
  [`CARL`, `climbed`],
  [`Frank`, `ate`],
  [`frank`, `strolled`],
  [`carl`, `Slept`],
  [`Frank`, `  `],
]

const cleanSlothDiaryEntries = pipe(
  messySlothDiaryEntries,
  map(([sloth, activity]) => [sloth, activity.trim()]),
  filter(([, activity]) => activity.length > 0),
  map(entry => entry.map(string => string.toLowerCase())),
  reduce(toArray()),
)
console.log(cleanSlothDiaryEntries)
//=> [ [ 'carl', 'slept' ], [ 'phil', 'ate' ], [ 'carl', 'climbed' ], ... ]

const uniqueActiviesPerSloth = reduce(
  toGrouped(toSet(), toMap()),
  cleanSlothDiaryEntries,
)
console.log(uniqueActiviesPerSloth)
//=> Map(3) {
//=>   'carl' => Set(2) { 'slept', 'climbed' },
//=>   'phil' => Set(1) { 'ate' },
//=>   'frank' => Set(2) { 'ate', 'strolled' }
//=> }

Some sequential asynchronous operations:

import { createReadStream } from 'node:fs'
import readline from 'node:readline'
import got from 'got'
import { chunkAsync, forEachAsync, mapAsync, pipe } from 'lfi'

const filename = `every-sloth-name.txt`

await pipe(
  readline.createInterface({
    input: createReadStream(filename, { encoding: `utf8` }),
    crlfDelay: Infinity,
  }),
  chunkAsync(4),
  mapAsync(async slothSquad => {
    const [adjective] = await got(
      `https://random-word-form.herokuapp.com/random/adjective`,
    ).json()
    return `${slothSquad.slice(0, 3).join(`, `)}, and ${slothSquad.at(
      -1,
    )} are ${adjective}`
  }),
  forEachAsync(console.log),
)
//=> george, phil, carl, and frank are jolly!
//=> scott, jerry, ralph, and mike are infinite!
// ...

Some concurrent asynchronous operations:

import { createReadStream } from 'node:fs'
import readline from 'node:readline'
import got from 'got'
import { asConcur, chunkAsync, forEachConcur, mapConcur, pipe } from 'lfi'
import limitConcur from 'limit-concur'

const filename = `every-sloth-name.txt`

await pipe(
  readline.createInterface({
    input: createReadStream(filename, { encoding: `utf8` }),
    crlfDelay: Infinity,
  }),
  chunkAsync(4),
  // Query for the adjectives of each group concurrently rather than sequentially!
  asConcur,
  mapConcur(
    // At most 4 requests at a time!
    limitConcur(4, async slothSquad => {
      const [adjective] = await got(
        `https://random-word-form.herokuapp.com/random/adjective`,
      ).json()
      return `${slothSquad.slice(0, 3).join(`, `)}, and ${slothSquad.at(
        -1,
      )} are ${adjective}`
    }),
  ),
  forEachConcur(console.log),
)
//=> george, phil, carl, and frank are jolly!
//=> scott, jerry, ralph, and mike are infinite!
// ...

API

See the documentation for the full list of available functions and classes.

All non-variadic functions are curried.

FAQ

What Is a Concurrent Iterable?

A concurrent iterable (represented by the ConcurIterable type) is a collection of values that can be iterated concurrently.

It is implemented as a function that:

How Do Concurrent Iterables Work?

The asConcur function constructs a concur iterable from a normal iterable. Here is a simplified implementation:

const asConcur = iterable => apply =>
  Promise.all(Array.from(iterable, value => apply(value)))

The implementation returns a function that calls the apply callback for each value in the iterable and returns a promise that resolves once all values have been handled (taking into consideration that the handling of apply may be asynchronous!).

We can iterate over concur iterables:

const concurIterable = asConcur([`sleep`, `climb`, `eat`])

await concurIterable(console.log)
//=> sleep
//=> climb
//=> eat

We can manually map and filter them:

import fs from 'node:fs/promises'

const transformedConcurIterable = apply =>
  concurIterable(async name => {
    const contents = await fs.readFile(`${name}.txt`, `utf8`)

    if (!contents.includes(`sloth`)) {
      return
    }

    await apply(contents)
  })

await transformedConcurIterable(console.log)

Or we can use lfi's awesome functions to map and filter them!

import fs from 'node:fs/promises'
import { filterConcur, forEachConcur, mapConcur, pipe } from 'lfi'

await pipe(
  concurIterable,
  mapConcur(name => fs.readFile(`${name}.txt`, `utf8`)),
  filterConcur(contents => contents.includes(`sloth`)),
  forEachConcur(console.log),
)

Are Concurrent Iterables Any Different Than Chaining p-map, p-filter, Etc.?

They are different!

Contributing

Stars are always welcome!

For bugs and feature requests, please create an issue.

License

MIT © Tomer Aberbach
Apache 2.0 © Google