Skip to main content

Recipes

tip

No recipe for what you're trying to do? File an issue or update the documentation!

How do I group values by a key?

If each key is associated with exactly one value, then you can use a keyed reducer:

import { map, pipe, reduce, toMap } from 'lfi'

console.log(
pipe(
[`sloth`, `lazy`, `sleep`],
// Create an iterable of key-value pairs
map(word => [word, word.length]),
// Collect the key-value pairs into a map
reduce(toMap()),
),
)
//=> Map(3) {
//=> 'sloth' => 5,
//=> 'lazy' => 4,
//=> 'sleep' => 5
//=> }
Playground
warning

If there are multiple key-value pairs with the same key, then only the last key-value pair is preserved.

If each key is associated with one or more values, then you can use toGrouped:

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

console.log(
pipe(
[`sloth`, `lazy`, `sleep`],
// Create an iterable of key-value pairs
map(word => [word.length, word]),
// Collect the values by key into a map where each group is an array
reduce(toGrouped(toArray(), toMap())),
),
)
//=> Map(2) {
//=> 5 => [ 'sloth', 'sleep' ],
//=> 4 => [ 'lazy' ]
//=> }
Playground

In both cases you can swap out both toArray and toMap for other reducers to collect the key-value pairs into different data structures.

How do I limit the concurrency of a concur iterable?

You can wrap the async callback you want to limit the concurrency of with limit-concur:

import { asConcur, mapConcur, pipe, reduceConcur, toArray } from 'lfi'
import limitConcur from 'limit-concur'

const API_URL = `https://random-word-form.herokuapp.com/random/adjective`

let pendingRequests = 0
console.log(
await pipe(
asConcur([`strawberry`, `max`, `bitsy`, `tommy`]),
mapConcur(
// At most 2 requests at a time
limitConcur(2, async sloth => {
console.log(++pendingRequests)
const [adjective] = await (await fetch(API_URL)).json()
console.log(--pendingRequests)
return `${adjective} ${sloth}`
}),
),
reduceConcur(toArray()),
),
)
//=> 1
//=> 2
//=> 1
//=> 2
//=> 1
//=> 2
//=> 1
//=> 0
// NOTE: This order may change between runs
//=> [
//=> 'kind strawberry',
//=> 'humble max',
//=> 'great bitsy',
//=> 'beautiful tommy'
//=> ]
Playground

How do I reduce an iterable to more than one result in one pass?

You can use toMultiple:

import { map, pipe, reduce, toCount, toJoin, toMultiple, toSet } from 'lfi'

console.log(
pipe(
[`sloth`, `lazy`, `sleep`],
map(word => word.length),
reduce(toMultiple([toSet(), toCount(), toJoin(`,`)])),
),
)
//=> [
//=> Set(2) { 5, 4 },
//=> 3,
//=> '5,4,5'
//=> ]

console.log(
pipe(
[`sloth`, `lazy`, `sleep`],
map(word => word.length),
reduce(
toMultiple({
set: toSet(),
count: toCount(),
string: toJoin(`,`),
}),
),
),
)
//=> {
//=> set: Set(2) { 5, 4 },
//=> count: 3,
//=> string: '5,4,5'
//=> }
Playground

Replacing common packages

note

These async replacements are most useful when the async operations are chained.

Also, most of these examples can be augmented with limitConcur if concurrency limiting is needed.

p-map and p-all

Use mapConcur and reduceConcur instead. For example:

import { asConcur, mapConcur, pipe, reduceConcur, toArray } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
mapConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
return (await response.json())[0].phonetic
}),
reduceConcur(toArray()),
),
)
// NOTE: This order may change between runs
//=> [ '/slɑθ/', '/ˈleɪzi/', '/sliːp/' ]
Playground

p-filter

Use filterConcur instead. For example:

import { asConcur, filterConcur, pipe, reduceConcur, toArray } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
filterConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
const [{ meanings }] = await response.json()
return meanings.some(meaning => meaning.partOfSpeech === `adjective`)
}),
reduceConcur(toArray()),
),
)
//=> [ 'lazy' ]
Playground

p-locate

Use findConcur instead (or findAsync for preserveOrder: true). For example:

import { asConcur, findConcur, orConcur, pipe } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`
const getPartsOfSpeech = async word => {
const response = await fetch(`${API_URL}/${word}`)
const [{ meanings }] = await response.json()
return meanings.map(meaning => meaning.partOfSpeech)
}

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
findConcur(async word => (await getPartsOfSpeech(word)).includes(`verb`)),
orConcur(() => `not found!`),
),
)
// NOTE: This word may change between runs
//=> sloth

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
findConcur(async word => (await getPartsOfSpeech(word)).includes(`adverb`)),
orConcur(() => `not found!`),
),
)
//=> not found!
Playground

p-race and p-any

Use firstConcur instead. For example:

import { asConcur, firstConcur, mapConcur, orConcur, pipe } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
mapConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
return (await response.json())[0].phonetic
}),
firstConcur,
orConcur(() => `not found!`),
),
)
// NOTE: This word may change between runs
//=> /ˈleɪzi/
Playground

To ignore errors like p-any, filter them out using filterMapConcur. For example:

import { asConcur, filterMapConcur, firstConcur, orConcur, pipe } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
filterMapConcur(async word => {
try {
const response = await fetch(`${API_URL}/${word}`)
return (await response.json())[0].phonetic
} catch {
return null
}
}),
firstConcur,
orConcur(() => `not found!`),
),
)
// NOTE: This word may change between runs
//=> /ˈleɪzi/
Playground

p-some

Use takeConcur instead. For example:

import {
asConcur,
mapConcur,
orConcur,
pipe,
reduceConcur,
takeConcur,
toArray,
} from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
mapConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
return (await response.json())[0].phonetic
}),
takeConcur(2),
reduceConcur(toArray()),
),
)
// NOTE: This word may change between runs
//=> [ '/ˈleɪzi/', '/slɑθ/' ]
Playground

To ignore errors like p-some, filter them out using filterMapConcur. For example:

import {
asConcur,
filterMapConcur,
orConcur,
pipe,
reduceConcur,
takeConcur,
toArray,
} from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
filterMapConcur(async word => {
try {
const response = await fetch(`${API_URL}/${word}`)
return (await response.json())[0].phonetic
} catch {
return null
}
}),
takeConcur(2),
reduceConcur(toArray()),
),
)
// NOTE: This word may change between runs
//=> [ '/ˈleɪzi/', '/slɑθ/' ]
Playground

p-reduce

Use reduceConcur instead. For example:

import { asConcur, mapConcur, orConcur, pipe, reduceConcur } from 'lfi'

const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
mapConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
return (await response.json())[0].phonetic
}),
// Without an initial value
reduceConcur((sentence, word) => `${sentence} and ${word}`),
orConcur(() => ``),
),
)
// NOTE: This order may change between runs
//=> /sliːp/ and /ˈleɪzi/ and /slɑθ/

console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
mapConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
return (await response.json())[0].phonetic
}),
// With an initial value
reduceConcur({
create: () => `words:`,
add: (content, word) => `${content}\n- ${word}`,
}),
),
)
// NOTE: This order may change between runs
//=> words:
//=> - /ˈleɪzi/
//=> - /sliːp/
//=> - /slɑθ/
Playground