Skip to main content

lfi is a lazy functional sync, async, and concurrent iteration library

Get started

🔀 Concurrent iteration

lfi’s concurrent iterables offer superior performance over p-map, p-filter, and others, because each item flows through the operations independently of other items.

import { asConcur, filterConcur, mapConcur, pipe, reduceConcur, toArray } from 'lfi'
import pFilter from 'p-filter'
import pMap from 'p-map'

// Hypothetical delays for each item
const mapDelays = [5, 1, 1]
const filterDelays = [1, 1, 5]

const delay = ms =>
new Promise(resolve => setTimeout(resolve, ms))
const mapFn = i =>
delay(mapDelays[i] * 1000).then(() => i)
const filterFn = i =>
delay(filterDelays[i] * 1000).then(() => true)

// Takes 6 seconds! Each item flows through the
// operations independently of other items
console.time(`with lfi`)
const withLfi = await pipe(
asConcur([0, 1, 2]),
mapConcur(mapFn),
filterConcur(filterFn),
reduceConcur(toArray()),
)
console.timeEnd(`with lfi`)

// Takes 10 seconds! The first item is a bottleneck
// because `p-map` waits for all callbacks
console.time(`without lfi`)
const withoutLfi = await pFilter(
await pMap([0, 1, 2], mapFn),
filterFn,
)
console.timeEnd(`without lfi`)
Playground

🦥 Lazy and memory efficient

lfi delays applying operations until their results are needed.

import { filter, flatMap, map, pipe, reduce, toSet } from 'lfi'
import zoo from 'zoo'

const getSlothNamesWithLfi = () =>
pipe(
zoo.exhibits,
// No arrays created anywhere here
flatMap(exhibit => exhibit.animals),
filter(animal => animal.species === `sloth`),
map(sloth => sloth.name),
// And then a set is created here
reduce(toSet()),
)
console.log(getSlothNamesWithLfi())

const getSlothNamesWithoutLfi = () => {
const slothNames = zoo.exhibits
// An array is created here...
.flatMap(exhibit => exhibit.animals)
// And here...
.filter(animal => animal.species === `sloth`)
// And here...
.map(sloth => sloth.name)
// And then a set is created here
return new Set(slothNames)
}
console.log(getSlothNamesWithoutLfi())
Playground

🌳 Small and tree shakeable

lfi is just 5.47 kB gzipped, or even smaller when tree shaking.

lfi’s functions are automatically curried and their parameters are perfectly arranged for use in the pipe function, which provides a syntax similar to method chaining without giving up tree shaking.

The result? The functions imported in the code below bundle to just 790 B gzipped!

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

const getSlothNamesByAge = () =>
pipe(
zoo.exhibits,
flatMap(exhibit => exhibit.animals),
filter(animal => animal.species === `sloth`),
map(sloth => [sloth.age, sloth.name]),
reduce(toGrouped(toSet(), toMap())),
)

console.log(getSlothNamesByAge())
//=> Map(3) {
//=> 7 => Set(2) {
//=> 'strawberry',
//=> 'bitsy'
//=> },
//=> 19 => Set(1) {
//=> 'max'
//=> },
//=> 24 => Set(1) {
//=> 'tommy'
//=> }
//=> }
Playground