--- 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): ```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 //=> } ``` ```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/' //=> } ``` ```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/' //=> } ``` :::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): ```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' ] //=> } ``` ```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' ] //=> } ``` ```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' ] //=> } ``` 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): ```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' //=> } ``` ```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' //=> } ``` ```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' //=> } ``` ## 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ɑθ/ ```