Recipes
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:
- Sync
- Async
- Concur
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
//=> }
import { asAsync, mapAsync, pipe, reduceAsync, toMap } from 'lfi'
const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`
console.log(
await pipe(
asAsync([`sloth`, `lazy`, `sleep`]),
// Create an async iterable of key-value pairs
mapAsync(async word => {
const response = await fetch(`${API_URL}/${word}`)
const [{ phonetic }] = await response.json()
return [word, phonetic]
}),
// Collect the key-value pairs into a map
reduceAsync(toMap()),
),
)
//=> Map(3) {
//=> 'sloth' => '/slɑθ/',
//=> 'lazy' => '/ˈleɪzi/',
//=> 'sleep' => '/sliːp/'
//=> }
import { asConcur, mapConcur, pipe, reduceConcur, toMap } from 'lfi'
const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`
console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
// Create a concur iterable of key-value pairs
mapConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
const [{ phonetic }] = await response.json()
return [word, phonetic]
}),
// Collect the key-value pairs into a map
reduceConcur(toMap()),
),
)
// NOTE: This order may change between runs
//=> Map(3) {
//=> 'sloth' => '/slɑθ/',
//=> 'lazy' => '/ˈleɪzi/',
//=> 'sleep' => '/sliːp/'
//=> }
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
:
- Sync
- Async
- Concur
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' ]
//=> }
import {
asAsync,
mapAsync,
pipe,
reduceAsync,
toArray,
toGrouped,
toMap,
} from 'lfi'
const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`
console.log(
await pipe(
asAsync([`sloth`, `lazy`, `sleep`]),
// Create an async iterable of key-value pairs
mapAsync(async word => {
const response = await fetch(`${API_URL}/${word}`)
const [{ meanings }] = await response.json()
return [meanings[0].partOfSpeech, word]
}),
// Collect the values by key into a map where each group is an array
reduceAsync(toGrouped(toArray(), toMap())),
),
)
//=> Map(2) {
//=> 'noun' => [ 'sloth', 'lazy' ],
//=> 'verb' => [ 'sleep' ]
//=> }
import {
asConcur,
mapConcur,
pipe,
reduceConcur,
toArray,
toGrouped,
toMap,
} from 'lfi'
const API_URL = `https://api.dictionaryapi.dev/api/v2/entries/en`
console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
// Create an async iterable of key-value pairs
mapConcur(async word => {
const response = await fetch(`${API_URL}/${word}`)
const [{ meanings }] = await response.json()
return [meanings[0].partOfSpeech, word]
}),
// Collect the values by key into a map where each group is an array
reduceConcur(toGrouped(toArray(), toMap())),
),
)
// NOTE: This order may change between runs
//=> Map(2) {
//=> 'noun' => [ 'sloth', 'lazy' ],
//=> 'verb' => [ 'sleep' ]
//=> }
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'
//=> ]
How do I reduce an iterable to more than one result in one pass?
You can use toMultiple
:
- Sync
- Async
- Concur
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'
//=> }
import {
asAsync,
flatMapAsync,
pipe,
reduceAsync,
toCount,
toJoin,
toMultiple,
toSet,
} 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(
asAsync([`sloth`, `lazy`, `sleep`]),
flatMapAsync(getPartsOfSpeech),
reduceAsync(toMultiple([toSet(), toCount(), toJoin(`,`)])),
),
)
//=> [
//=> Set(3) { 'noun', 'verb', 'adjective' },
//=> 6,
//=> 'noun,verb,noun,verb,adjective,verb'
//=> ]
console.log(
await pipe(
asAsync([`sloth`, `lazy`, `sleep`]),
flatMapAsync(getPartsOfSpeech),
reduceAsync(
toMultiple({
set: toSet(),
count: toCount(),
string: toJoin(`,`),
}),
),
),
)
//=> {
//=> set: Set(3) { 'noun', 'verb', 'adjective' },
//=> count: 6,
//=> string: 'noun,verb,noun,verb,adjective,verb'
//=> }
import {
asConcur,
mapConcur,
pipe,
reduceConcur,
toCount,
toJoin,
toMultiple,
toSet,
} 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`]),
mapConcur(getPartsOfSpeech),
reduceConcur(toMultiple([toSet(), toCount(), toJoin(`,`)])),
),
)
// NOTE: This order may change between runs
//=> [
//=> Set(3) { 'noun', 'verb', 'adjective' },
//=> 6,
//=> 'noun,verb,noun,verb,adjective,verb'
//=> ]
console.log(
await pipe(
asConcur([`sloth`, `lazy`, `sleep`]),
mapConcur(getPartsOfSpeech),
reduceConcur(
toMultiple({
set: toSet(),
count: toCount(),
string: toJoin(`,`),
}),
),
),
)
// NOTE: This order may change between runs
//=> {
//=> set: Set(3) { 'noun', 'verb', 'adjective' },
//=> count: 6,
//=> string: 'noun,verb,noun,verb,adjective,verb'
//=> }
Replacing common packages
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/' ]
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' ]
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!
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/
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/
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ɑθ/' ]
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ɑθ/' ]
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ɑθ/