Entendendo o mapa RxJS, mergeMap, switchMap e concatMap

Luuk Gruijs Blocked Unblock Seguir Seguindo 9 de janeiro

O mapeamento de dados é uma operação comum durante a gravação do seu programa. Quando você usa o RxJS em seu código para produzir seus fluxos de dados, é muito provável que você eventualmente precise mapear os dados para qualquer formato que precise. O RxJS vem com uma função de mapa 'normal', mas também possui funções como mergeMap, switchMap e concatMap, que se comportam de maneira um pouco diferente.

Foto de Dennis Kummer em Unsplash

O operador do mapa

O operador do mapa é o mais comum de todos. Para cada valor que o Observable emite, você pode aplicar uma função na qual você pode modificar os dados. O valor de retorno, nos bastidores, será reemitido como um Observable novamente para que você possa continuar usando-o em seu stream. Funciona praticamente da mesma forma que você usaria com Arrays. A diferença é que os Arrays sempre serão apenas Arrays e, durante o mapeamento, você obtém o valor do índice atual no Array. Com Observables, o tipo de dados pode ser de todos os tipos. Isso significa que você pode precisar fazer algumas operações adicionais na função de mapa Observable para obter o resultado desejado. Vamos ver alguns exemplos:

Primeiro criamos o nosso Observable com uma variedade de carros. Nós então subscrevemos este Observable 2 vezes. A primeira vez que modificamos nossos dados de forma que recebemos uma matriz de strings de marca e modelo concatenadas. Na segunda vez, modificamos nossos dados para obtermos apenas uma série de carros Porsche. Nos dois exemplos, usamos o operador do mapa Observable para modificar os dados que estão sendo emitidos pelo Observable. Retornamos o resultado de nossa modificação e o operador do mapa, por trás dos bastidores, cuida de embrulhar isso em um Observable novamente para que possamos assiná-lo mais tarde.

MergeMap

Agora, digamos que há um cenário em que temos um Observable que emite uma matriz e, para cada item da matriz, precisamos buscar dados do servidor.

Podemos fazer isso inscrevendo-se na matriz e, em seguida, configurar um mapa que chame uma função que manipule a chamada da API e, em seguida, assine o resultado. Isso pode parecer com o seguinte:

Nossa função de mapa retorna o valor da função getData. Nesse caso, isso é um Observável. Isso, no entanto, cria um problema porque agora estamos lidando com um Observável adicional.

Para esclarecer ainda mais: temos from([1,2,3,4]) como nosso 'externo' Observável, e o resultado do getData() como nosso 'interno' Observável. Em teoria, temos de nos inscrever tanto no nosso Observable externo quanto no interno para obter os dados. Isso poderia gostar disso:

Como você pode imaginar, isso está longe de ser ideal, já que temos que ligar para o Subscribe duas vezes. É aqui que mergeMap vem para o resgate. MergeMap essencialmente é uma combinação de mergeAll e map. MergeAll cuida de se inscrever no 'interior' Observable para que não tenhamos mais que assinar duas vezes, pois o mergeAll mescla o valor do 'inner' Observable no 'outer' Observable. Isso poderia ser assim:

Isso já é muito melhor, mas como você já deve ter imaginado, o mergeMap seria a melhor solução para isso. Aqui está o exemplo completo:

Você também pode ter ouvido falar sobre flatMap. FlatMap é um alias do mergeMap e se comporta da mesma maneira. Não fique confuso lá!

SwitchMap

O SwitchMap tem um comportamento similar, pois também irá se inscrever no Inner Observable para você. No entanto switchMap é uma combinação de switchAll e map. SwitchAll cancela a assinatura anterior e assina a nova. Para nosso cenário em que queremos fazer uma chamada de API para cada item na matriz do Observable 'externo', o switchMap não funciona bem, pois cancelará as 3 primeiras assinaturas e lidará apenas com a última. Isso significa que obteremos apenas um resultado. O exemplo completo pode ser visto aqui:

Embora o switchMap não funcione para nosso cenário atual, ele funcionará para outros cenários. Por exemplo, seria útil se você compusesse uma lista de filtros em um fluxo de dados e executasse uma chamada de API quando um filtro fosse alterado. Se as alterações de filtro anteriores ainda estiverem sendo processadas enquanto uma nova alteração já foi feita, ela cancelará a assinatura anterior e iniciará uma nova assinatura na última alteração. Um exemplo pode ser visto aqui:

Como você pode ver no console, o getData está registrando apenas uma vez com todos os parâmetros. Isso nos salvou 3 chamadas de API.

ConcatMap

O último exemplo é concatMap. Como você poderia esperar, o concatMap também assina o Inner Observable para você. Mas, ao contrário do switchMap, que cancela a assinatura do Observable atual se um novo Observable entrar, o concatMap não assinará o próximo Observable até que o atual seja concluído. O benefício disso é que a ordem na qual os Observáveis estão emitindo é mantida. Para demonstrar isso:

A função getData tem um atraso aleatório entre 1 e 10000 milissegundos. Se você verificar os logs, verá que os operadores map e mergeMap registrarão o valor retornado e não seguirão o pedido original. Por outro lado, o concatMap registra os valores no mesmo valor que eles foram iniciados.

Conclusão

O mapeamento de dados para o formato que você precisa é uma tarefa comum. O RxJS vem com alguns operadores muito interessantes que ajudam você a fazer o trabalho. Para recapitular: map é para mapear valores 'normais' para qualquer formato que você precise. O valor de retorno será empacotado em um Observable novamente, para que você possa continuar usando-o em seu fluxo de dados. Quando você tem que lidar com um Observable 'interno', é mais fácil usar mergeMap, switchMap ou concatMap. Use mergeMap se você simplesmente quiser achatar os dados em um Observable, use switchMap se precisar achatar os dados em um Observable, mas só precisar do último valor e usar concatMap se precisar achatar os dados em um Observable e a ordem for importante para você.