Flat/FlatMap do Javascript na Prática
Tags: webdev, javascript, node
Published: 2020-06-23 00:38:02

Aprendendo a utilizar as novas operações de Array do Javascript

#### A proposta

Foram confirmadas recentemente as novas propostas de funcionalidades que chegaram no [estágio 4](https://tc39.github.io/process-document/) do [ECMAScript](https://pt.wikipedia.org/wiki/ECMAScript), significando que estarão na proxima especificação oficial e terão sua implementação na linguagem!!

Dentre elas temos dois novos bebês ❤ chamados Array.prototype.flat() e o Array.prototype.flatMap(), baseados na [proposta do Michael Ficarra, Brian Terlson, Mathias Bynens](https://github.com/tc39/proposal-flatMap). Mas qual a importancia deles e como podemos utiliza-los?

O **_flatMap_** _(Muitas vezes chamado de concatMap, fmap ou >>= em outras linguagens)_ é um pattern comum que vem da programação funcional de linguagens como _Scala_ ou _Haskell._ Esta operação de _array_ pode nos ajudar solucionar problemas em que temos de iterar por exemplo, arrays com itens complexos. Você pode já ter ouvido falar de [implementações similares de Flatmap por exemplo no RxJS para lidar com Observables](https://www.learnrxjs.io/operators/transformation/mergemap.html).

Mas diferente de outras linguagens, que utilizam o **_flatMap_** como uma operação para manipular **_objetos, strings_** , tendo usos até mesmo como meio de lidar com valores **_opcionais_** e **_monadas._** A sua implementação no Javascript se limita a **apenas operações de _arrays._**

Estamos familiares com funções como o [**_map_** , **_filter_** e **_reduce_**](http://equinocios.com/swift/2017/03/13/Introducao-e-casos-de-uso-Map-Filter-e-Reduce/) que são responsáveis por transformar os elementos do arrays em novos valores a partir de uma função.

```javascript
// Adicionando +1 para todos os elementos do array
[1, 2, 3].map(x => x + 1); // [2, 3, 4]

// Filtrando elementos impares do array
[1, 2, 3].filter(x => x % 2 == 1) // [1 , 3]

// Somando os elementos do array
[1, 2, 3].reduce((acc, x) => acc + x) // 6
```

Similarmente, o **_flatMap_** recebe uma função como argumento e funde os conceitos de **_flat_** com o já conhecido **_map_** _._

Mas o que é o flat?

#### **Array.prototype.flat()**

O Array.prototype.flat(), também conhecido como _flatten_, tem o intuito de deixar nosso array plano recursivamente em uma profundidade especificada como argumento, ou seja, é uma operação que concatena os elementos de um _array._

Por padrão a função de _flat_ planifica em um nível(.flat(1)) como no exemplo:

```javascript
[1, 2, [3, 4]].flat();
// [1, 2, 3, 4]

[1, 2, [3, 4, [5, 6]]].flat();
// [1, 2, 3, 4, [5, 6]]
```

Passando o número 2 como argumento a função fica plana em 2 níveis.

```javascript
[1, 2, [3, 4, [5, 6]]].flat(2);
// [1, 2, 3, 4, 5, 6]
```

#### Alguns usos praticos do Flat

**Concatenando arrays**

Supondo dois arrays contendo alguns números que devem ser concatenados em apenas um array.

```javascript
const array1 = [1, 2, 3]
const array2 = [4, 5, 6]
```

Uma forma de fazer isso seria mutar algum destes arrays e utilizar a operação **_push_** para inserir os valores do outro array dentro do outro.

```javascript
array1.push(...array2)
array1 // [1, 2, 3, 4, 5, 6]_
```

Outro método comum caso eu queria criar um novo array seria utilizar o [_spread_](https://medium.com/magnetis-backstage/p%C3%ADlulas-de-javascript-operador-spread-e-par%C3%A2metro-rest-afd1f0266036) dos arrays dentro de um novo array concatenando seus elementos.

```javascript
const array3 = [
 …array1,
 …array2
] // [1, 2, 3, 4, 5, 6]_
```

A operação de **_flat_** nos introduz uma maneira interessante sem a necessidade de _spreads_ para concatenar o elementos deste array.

```javascript
[array1, array2].flat()
```

**Condicionalmente inserindo valores em um array.**

Supondo que caso uma condição seja verdadeira, devo inserir um valor dentro de um array.

Uma forma mais sucinta seria ao invés de um “if”, considerar essa condicional na propria criação array, colocando um ternário no proprio array. Caso a condição é verdadeira, insere o valor ‘a’, caso contrário insere _null_.

```javascript
const cond = false;
const arr = [
  'b',
  (cond ? 'a' : null),
]; // ['b', null]
```

Em condições positivas teremos o elemento esperado ‘a’, mas caso contrário teremos um array sujo com valores _“null”_ e para isso seria necessário de alguma forma filtrar esses valores.

```javascript
arr.filter(_ => _ !== null) // ['b']
```

Com o _flat_ podemos fazer de forma simples a inserção de valores caso a condição é verdadeira com uma condicional (cond  **?** ['a']  **:** []). Pois já que o proprio flat concatena arrays, a concatenação de um array vazio caso uma condição falsa não geraria a insersão de valores desnecessários.

```javascript
const cond = false;
const arr = [
  (cond ? ['a'] : []),
  'b',
].flat(); // ['b']
```

**Criando uma copia de um array**

Quando queremos criar uma copia de um _arr_ mudando sua referencia.

```javascript
const x = [1, 2, 3, [4]]

const y = x.flat(0)
y[0] = 3

x // [1,2,3,[4]]
y // [3,2,3,[4]]
```

Note que isso vai apenas retornar uma “_shallow copy_”. Ou seja, objetos dentro do array não serão clonados.

#### **Array.prototype.flatMap()**

O **_flatMap_** é basicamente um **_map_** com **_flat_**. Como assim?

Com o **_map_** , cada elemento do array é iterado e apartir de uma funçãof retorna um novo array com cada um desses valores transformados. A função fque recebe um elemento _input_ e torna um elemento _output_.

Com o **_flatMap_** , cada elemento é iterado e apartir de uma função f retorna um array de valores. A função fque recebe um elemento _input_ e e cada elemento pode ser transformado em nenhum ou mais elementos de _output_.

![](https://cdn-images-1.medium.com/max/1024/1*aVV-YQA8RRx0R6ZV32MGEA.jpeg)<figcaption>Relação entre Map(One to one) e FlatMap(One to many)</figcaption>

Ambos **_flatMap_** e **_map_** recebem uma função f como argumento que gera um novo array de retorno com base nos items do array de origem.

Sequencialmente o **_flatMap_** seria similar a aplicação de uma função dentro de um **_map_** seguido de uma operação de **_flat_** planificando o _Array_.

```javascript
[1, 2, 3]
 .map(item => [item, item \* 100]); //[[1, 100], [2, 200], [3, 300]]
 .flat() // [1, 100, 2, 200, 3, 300]

[1, 2, 3].flatMap(item => [item, item * 100]);
// [1, 100, 2, 200, 3, 300]

// Mesma operação :)
```

Similarmente, usando o **_flatMap_** com uma função identidade(x => x), em que inutilizamos o seu **_map_** , temos exatamente o que seria apenas um **_flat_**.  
As seguintes operações são equivalentes:

```javascript
arr.flatMap(x => x)
arr.map(x => x).flat()
arr.flat()
```

#### Alguns usos praticos do FlatMap

**Filtrar e transformar arrays  
Exemplo 1**

Podemos utilizar a operação de flapMap() como meio de filtrar elementos em arrays e transforma-los.

Supondo uma array de numeros de 1 a 10.

```javascript
const x = [1, 2 ,3 ,4, 5, 6, 7, 8, 9, 10]
```

Queremos transformar este array em apenas numeros **antecessores** de **numeros primos**. Supondo que eu tenho uma função _isPrime_ que me retorna verdadeiro ou falso caso o numéro é um primo. Podemos primeiramente utilizar a função _filter_ para filtrar os valores em apenas primos_._

```javascript
x.filter(i => isPrime(i)) // [2, 3, 5, 7]
```

Porém para listar os antecessores do array teriamos de **novamente** iterar pelos itens para retornar um novo array com cada valor subtraido por 1.

```javascript
x.filter(i => isPrime(i))
 .map(i => i - 1) // [1, 2, 4, 6]
```

Com o **_flatMap_** podemos fazer ambas as operações em apenas uma iteração de array em que com uma operação ternária retornamos ou um array com o valor subtraido por 1 ou uma array vazio.

```javascript
x.flatMap(i => isPrime(i) ? [i — 1] : []) // [1, 2, 4, 6]
```

Sendo assim: é um **_map_** , que iteraria os 10 elementos do _array_ e geraria 10 _arrays_, seguido de um **_flat_** planificando em apenas um _array_:

```javascript
x.map(i => isPrime(i) ? [i — 1] : []) // [[],[1],[2],[],[4],[],[6]..] 
 .flat() // [1, 2, 4, 6]
```

**Exemplo 2**

Tenho um array de ids de objetos e uma propriedade booleana indicando se este item deve ou não ser listado, se sim devo dar fetch nesta propriedade.

```javascript
const items = [
 { id : 1, toList : true}
 { id : 2, toList : false},
]
```

Sem o **_flatMap_** uma solução viavel seria utilizar o **_filter_** para filtrar caso a propriedade _toList_ é verdadeira e em seguida seria necessário utilizar um **_map_** para efetivamente dar fetch nesses ids.

```javascript
items
 .filter(i => i.toList)
 .map(i => fetch(i.id)) // [Promise]
```

Com apenas um flatMap podemos resolver este problema criando uma função em que caso o _toList_ é verdadeiro ele retorna um array com o fetch do id, caso contrário retorna um array vazio que será concatenado.

```javascript
Promise.all(items.flatMap(i => i.toList
 ? [fetch(i.id)]
 : [])) // [...]
```

**Exemplo 3**

Podemos usar para extrairmos apenas um tipo de dado de um objeto em tratativas. Por exemplo, em um array de objetos cuja tratativa de erros de um **_try / catch_** retorna apenas os valores dos resultados ou apenas os erros.

```javascript
const results = arr.map(x => {
try {
  return { value: fazerAlgo(x) };
    } catch (e) {
return { error: e };
    }
});
```

**_flatMap_** pode ser nosso aliado para podermos extrair apenas os erros ou apenas os valores especificos destes resultados por meio de uma operação ternária:

```javascript
const values = results. flatMap(
    result => result.value ? [result.value] : []);

const errors = results.flatMap(
    result => result.error ? [result.error] : []);
```

**Pegando elementos de um array de objetos com arrays aninhados.**

Supondo que tenho um array de objetos de cestas de frutas em que dentro do objetos listamos em “itens” as frutas dentro da cesta.

```javascript
const cestas = [
 { id : 1, itens : [“Maça”, “Banana”]},
 { id : 2, itens : [“Banana”, “Abacaxi”]}
]
```

Se quero listar todas as frutas dentro de cestas no map seria necessário iterar pelo array e pegar a propriedade “itens” de cada objeto.

```javascript
cestas.map(x => x.itens) // [Array(2), Array(2)]
```

Apenas com o map teriamos _arrays_ de _arrays_.

```javascript
cestas.flatMap(x => x.itens) // [“Maça”, “Banana”, “Banana”, “Abacaxi”]
```

Com o **_flatMap_** já temos a concatenação dos elementos do array e conseguimos obter todos os elementos listados dentro de objetos.

**Indexando em lista**

Supondo uma lista de compras, para listá-las entre vírgulas em um componente “GroceryList” podemos utilizar o **_flatMap._** A função cujo metodo recebe pode ter um segundo argumento com o index do array assim como o **_map_** ou **_filter_**. Por exemplo:

```javascript
['Foo','Bar'].map((x, index) => `${index}${x}`); _// ['0Foo', '1Bar']_
```

Quando retornamos um array desta função, os seus elementos são concatenados e podemos adicionar elementos condicionais (como por exemplo, a vírgula após o primeiro elemento da lista).

```javascript
class GroceryList extends React.Component {
render () {
const {groceries, handleClick} = this.props;
return groceries. flatMap (
            (_food_, _index_) => [
                ...(_index_ === 0 ? [] : [', ']),
                <a key={_index_} href=""
                   onClick={e => handleClick(_food_, e)}>
                   {_food_}
                </a>,
            ]);
    }
}
```

#### **Suporte de Navegadores**

O **_flat_** e o **_flatMap_** já estão com suporte nos principais navegadores(_Chrome 69, Firefox 62, Opera 56, Safari 12, Android WebView 69_) e na versão _11.0.0_ do _NodeJs ❤️🧡💛💚💙💜._

É possivel tambem importar proposals pelo _Babel 7_. Pelo _FlatMap_ já estar em _stage 4_ é[preciso importar especificamente a funcionalidade.](https://babeljs.io/docs/en/v7-migration#remove-proposal-polyfills-in-babel-polyfill-https-githubcom-babel-babel-issues-8416)

#### Conclusão

Cada vez mais vemos mudanças para agradar todas as formas / paradigmas do Javascript. Desde 2015 vemos a linguagem suportando outros estilos de orientação a objetos, e agora vemos a adição de elementos comuns de linguagens funcionais como **_FlatMap_** e quem sabe futuramente o [**_Pipeline Operator_**](https://github.com/tc39/proposal-pipeline-operator), [**_Pattern Matching_**](https://github.com/tc39/proposal-pattern-matching) e [**_Partial Application_**](https://github.com/tc39/proposal-partial-application) 🤞.

#### Espero que você tenha gostado do artigo ❤

**Quem gostou bate palminha pra que o artigo alcançe outros amiguinhos.  
👏 👏 👏 👏 👏 👏 👏 👏 👏**

- [#SmooshGate FAQ | Web | Google Developers](https://developers.google.com/web/updates/2018/03/smooshgate)
- [Array.prototype.flatMap & Array.prototype.flat](https://tc39.github.io/proposal-flatMap/)