---
sidebar_position: 2
---

import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'

# Recipes

:::tip

No recipe for what you're trying to do?
[File an issue](https://github.com/TomerAberbach/lfi/issues/new?title=[Recipe]%20)
or
[update the documentation](https://github.com/TomerAberbach/lfi/tree/main/website/docs/recipes.mdx)!

:::

## How do I group values by a key?

If each key is associated with exactly one value, then you can use a
[keyed reducer](/docs/concepts/reducer#keyedreducer-and-asynckeyedreducer):

<Tabs groupId='protocol' queryString>
<TabItem value='sync' label='Sync'>

```js playground
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
//=> }
```

</TabItem>
<TabItem value='async' label='Async'>

```js playground
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/'
//=> }
```

</TabItem>
<TabItem value='concur' label='Concur'>

```js playground
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/'
//=> }
```

</TabItem>
</Tabs>

:::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`](/docs/concepts/reducer#togrouped):

<Tabs groupId='protocol' queryString>
<TabItem value='sync' label='Sync'>

```js playground
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' ]
//=> }
```

</TabItem>
<TabItem value='async' label='Async'>

```js playground
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' ]
//=> }
```

</TabItem>
<TabItem value='concur' label='Concur'>

```js playground
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' ]
//=> }
```

</TabItem>
</Tabs>

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`](https://www.npmjs.com/package/limit-concur):

```js playground
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`](/docs/concepts/reducer#tomultiple):

<Tabs groupId='protocol' queryString>
<TabItem value='sync' label='Sync'>

```js playground
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'
//=> }
```

</TabItem>
<TabItem value='async' label='Async'>

```js playground
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'
//=> }
```

</TabItem>
<TabItem value='concur' label='Concur'>

```js playground
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'
//=> }
```

</TabItem>
</Tabs>

## Replacing common packages

:::note

These async replacements are most useful
[when the async operations are chained](/docs/concepts/concurrent-iterable#how-is-it-different-from-chaining-p-map-p-filter-and-others).

Also, most of these examples can be augmented with
[`limitConcur`](https://www.npmjs.com/package/limit-concur) if concurrency
limiting is needed.

:::

### [`p-map`](https://github.com/sindresorhus/p-map) and [`p-all`](https://github.com/sindresorhus/p-all)

Use [`mapConcur`](/docs/api/variables/mapConcur) and
[`reduceConcur`](/docs/api/variables/reduceConcur) instead. For example:

```js playground
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`](https://github.com/sindresorhus/p-filter)

Use [`filterConcur`](/docs/api/variables/mapConcur) instead. For example:

```js playground
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`](https://github.com/sindresorhus/p-locate)

Use [`findConcur`](/docs/api/variables/findConcur) instead (or
[`findAsync`](/docs/api/variables/findAsync) for `preserveOrder: true`). For
example:

```js playground
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`](https://github.com/sindresorhus/p-race) and [`p-any`](https://github.com/sindresorhus/p-any)

Use [`firstConcur`](/docs/api/variables/firstConcur) instead. For example:

```js playground
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`](/docs/api/variables/filterMapConcur). For example:

```js playground
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`](https://github.com/sindresorhus/p-some)

Use [`takeConcur`](/docs/api/variables/firstConcur) instead. For example:

```js playground
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`](/docs/api/variables/filterMapConcur). For example:

```js playground
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`](https://github.com/sindresorhus/p-reduce)

Use [`reduceConcur`](/docs/api/variables/reduceConcur) instead. For example:

```js playground
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ɑθ/
```
