Por que precisamos usar flatMap?

92

Estou começando a usar o RxJS e não entendo porque neste exemplo precisamos usar uma função como flatMapou concatAll; onde está a matriz de matrizes aqui?

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(url => {console.log(url)})

Se alguém puder explicar visualmente o que está acontecendo, será muito útil.

user233232
fonte
1
essa resposta é ótima por causa das referências valiosas que ela fornece, mas a terminologia do rxjs não é traduzida bem para o inglês. (as fotos são melhores). É por isso que eu recomendo executar exemplos simples como este, ou exemplos mais complexos no repo rxjs e adicionar operadores ".do" antes e depois de um mapa plano e operador de mapa e, em seguida, apenas definir um ponto de interrupção com o depurador do Chrome. você verá instantaneamente que cada um produz uma saída diferente
HipsterZipster
5
Acho que se flatMaptivesse sido nomeado mapThenFlatten, seria menos confuso.
cabra

Respostas:

73

Quando comecei a olhar Rxjstambém tropecei naquela pedra. O que me ajudou foi o seguinte:

  • documentação do reactivex.io. Por exemplo, para flatMap: http://reactivex.io/documentation/operators/flatmap.html
  • documentação de rxmarbles: http://rxmarbles.com/ . Você não vai encontrar flatMaplá, você deve olhar para mergeMap(outro nome).
  • a introdução ao Rx que você estava perdendo: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 . Ele aborda um exemplo muito semelhante. Em particular, aborda o fato de que uma promessa é semelhante a um observável que emite apenas um valor.
  • finalmente olhando para as informações de tipo do RxJava. Javascript não está sendo digitado não ajuda aqui. Basicamente, se Observable<T>denota um objeto observável que empurra valores do tipo T, então flatMappega uma função do tipo T' -> Observable<T>como seu argumento e retorna Observable<T>. maprecebe uma função de tipo T' -> Te retorna Observable<T>.

    Voltando ao seu exemplo, você tem uma função que produz promessas a partir de uma string de url. Então T' : string, e T : promise. E pelo que dissemos antes promise : Observable<T''>, então T : Observable<T''>, com T'' : html. Se você colocar essa função de produção de promessa map, obterá Observable<Observable<T''>>quando o que quiser é Observable<T''>: você deseja que o observável emita os htmlvalores. flatMapé chamado assim porque nivela (remove uma camada observável) do resultado map. Dependendo da sua formação, isso pode parecer chinês para você, mas tudo ficou claro para mim com a digitação das informações e o desenho aqui: http://reactivex.io/documentation/operators/flatmap.html .

user3743222
fonte
2
Eu esqueci de mencionar que você deve ser capaz de simplificar return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));a return jQuery.getJSON(requestUrl);como flatMaptambém aceita a função de seletor que retorna uma função promessa ou seja, do tipo T' -> Promise.
user3743222
2
Uau, esse GitHub Gist ( gist.github.com/staltz/868e7e9bc2a7b8c1f754 ) é fantástico. Eu o recomendo para qualquer pessoa que trabalhe com qualquer biblioteca ReactiveX como RxJS.
Jacob Stamm
@JacobStamm eu concordo. Apenas torna as coisas mais fáceis.
CruelEngine
O que significa esta sintaxe T’ -> T:? Eu entendo o Tcomo um genérico, mas o que é apóstrofo e seta não gorda?
1252748
Você pode substituir T 'por X ou Y sem alterar o significado em qualquer lugar da resposta. A seta é a notação de Haskell para assinatura de tipo. Portanto, T '-> T é a assinatura de uma função que pega um elemento do tipo T' e retorna um elemento do tipo T
usuário3743222
124
['a','b','c'].flatMap(function(e) {
    return [e, e+ 'x', e+ 'y',  e+ 'z'  ];
});
//['a', 'ax', 'ay', 'az', 'b', 'bx', 'by', 'bz', 'c', 'cx', 'cy', 'cz']


['a','b','c'].map(function(e) {
    return [e, e+ 'x', e+ 'y',  e+ 'z'  ];
});
//[Array[4], Array[4], Array[4]]

Você usa flatMap quando tem um Observable cujos resultados são mais Observables.

Se você tiver um observável que é produzido por outro observável, não poderá filtrar, reduzir ou mapeá-lo diretamente porque você tem um Observável e não os dados. Se você produzir um observável, escolha flatMap em vez de mapa; então você está bem.

Como no segundo trecho, se você estiver fazendo uma operação assíncrona, precisará usar flatMap.

var source = Rx.Observable.interval(100).take(10).map(function(num){
    return num+1
});
source.subscribe(function(e){
    console.log(e)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>

var source = Rx.Observable.interval(100).take(10).flatMap(function(num){
    return Rx.Observable.timer(100).map(() => num)
});
source.subscribe(function(e){
    console.log(e)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.1/Rx.min.js"></script>

serkan
fonte
31

flatMap transforma os itens emitidos por um Observável em novos Observáveis ​​e, em seguida, nivela as emissões desses em um único Observável.

Verifique o cenário abaixo, onde get("posts")retorna um Observable que é "nivelado" por flatMap.

myObservable.map(e => get("posts")).subscribe(o => console.log(o));
// this would log Observable objects to console.  

myObservable.flatMap(e => get("posts")).subscribe(o => console.log(o));
// this would log posts to console.
Emmanuel Osimosu
fonte
2
Boa resposta simples. Acho que isso pode ser o melhor.
vaughan
"flatMap transforma os itens emitidos por um Observable em novos Observables e, em seguida, nivela as emissões desses em um único Observable." Isso é excelente.
MBak
31

As pessoas tendem a complicar demais as coisas , dando a definição que diz:

flatMap transforma os itens emitidos por um Observável em Observáveis ​​e, em seguida, aplaina as emissões desses em um único Observável

Juro que esta definição ainda me confunde, mas vou explicá-la da maneira mais simples que é usando um exemplo

Nossa situação : temos um observável que retorna dados (URL simples) que usaremos para fazer uma chamada HTTP que retornará um observável contendo os dados de que precisamos para que você possa visualizar a situação assim:

Observable 1
    |_
       Make Http Call Using Observable 1 Data (returns Observable_2)
            |_
               The Data We Need

como você pode ver, não podemos alcançar os dados de que precisamos diretamente, então a primeira maneira de recuperar os dados podemos usar apenas assinaturas normais como esta:

Observable_1.subscribe((URL) => {
         Http.get(URL).subscribe((Data_We_Need) => {
                  console.log(Data_We_Need);
          });
});

isso funciona, mas como você pode ver, temos que aninhar assinaturas para obter nossos dados, isso atualmente não parece ruim, mas imagine que temos 10 assinaturas aninhadas que se tornariam impossíveis de manter.

então, a melhor maneira de lidar com isso é usar o operador flatMapque fará a mesma coisa, mas nos faz evitar essa assinatura aninhada:

Observable_1
    .flatMap(URL => Http.get(URL))
    .subscribe(Data_We_Need => console.log(Data_We_Need));
Hamed Baatour
fonte
19

Simples:

[1,2,3].map(x => [x, x * 10])
// [[1, 10], [2, 20], [3, 30]]

[1,2,3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]]
drpicox
fonte
16

Não é uma matriz de matrizes. É um observável de observável (s).

O seguinte retorna um fluxo observável de string.

requestStream
  .map(function(requestUrl) {
    return requestUrl;
  });

Enquanto isso retorna um fluxo observável de fluxo observável de json

requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

flatMap nivela o observável automaticamente para nós, para que possamos observar o fluxo json diretamente

Lucius
fonte
3
É difícil entender esse conceito, você pode adicionar comentários ao visual o que você quer dizer "retorna um fluxo observável de fluxo observável de json". obrigado.
user233232
@ user233232, como [x, x, x, x] a [[xxx], [[xxx], [xxx]]]
serkan
A chave para entender a primeira frase é entender que flatMap(e map) não são especiais para matrizes. É possível definir essas operações em qualquer container ou wrapper genérico, incluindo arrays, dicionários, "opcionais", fluxos reativos, promessas, ponteiros e até as próprias funções. Esta é uma propriedade emergente de uma construção matemática chamada mônada. Todos os exemplos acima atendem aos requisitos para ser uma mônada e, portanto, todos podem receber uma definição de mape a flatMap(com algumas ressalvas).
mklbtz de
14

Aqui, para mostrar a implementação equivalente de um flatMap usando assinantes.

Sem flatMap:

this.searchField.valueChanges.debounceTime(400)
.subscribe(
  term => this.searchService.search(term)
  .subscribe( results => {
      console.log(results);  
      this.result = results;
    }
  );
);

Com flatMap:

this.searchField.valueChanges.debounceTime(400)
    .flatMap(term => this.searchService.search(term))
    .subscribe(results => {
      console.log(results);
      this.result = results;
    });

http://plnkr.co/edit/BHGmEcdS5eQGX703eRRE?p=preview

Espero que possa ajudar.

Olivier.

olivier cherrier
fonte
13

Um Observable é um objeto que emite um fluxo de eventos: Next, Error e Completed.

Quando sua função retorna um Observable, ela não está retornando um stream, mas uma instância de Observable. O flatMapoperador simplesmente mapeia essa instância para um fluxo.

Esse é o comportamento de flatMapquando comparado a map: Executar a função fornecida e nivelar o objeto resultante em um fluxo.

AkkarinZA
fonte
7

Com flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .flatMap(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(json => {console.log(json)})

Sem flatMap

var requestStream = Rx.Observable.just('https://api.github.com/users');

var responseMetastream = requestStream
  .map(function(requestUrl) {
    return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));
  });

responseMetastream.subscribe(jsonStream => {
  jsonStream.subscribe(json => {console.log(json)})
})
Thanawat
fonte
0

flatMap transforma os itens emitidos por um Observável em Observáveis ​​e, em seguida, aplaina as emissões desses em um único Observável

Eu não sou estúpido, mas tive que ler isso 10 vezes e ainda não entendi. Quando leio o snippet de código:

[1,2,3].map(x => [x, x * 10])
// [[1, 10], [2, 20], [3, 30]]

[1,2,3].flatMap(x => [x, x * 10])
// [1, 10, 2, 20, 3, 30]

então eu pude entender o que está acontecendo, ele faz duas coisas:

flatMap :

  1. map : transform *) itens emitidos em Observables.
  2. plano : em seguida, mescle esses Observáveis ​​como um Observável.

*) A palavra de transformação diz que o item pode ser transformado em outra coisa.

Em seguida, o operador de mesclagem fica claro para, ele faz o achatamento sem o mapeamento. Por que não chamá-lo de mergeMap ? Parece que também existe um mergeMap Alias com esse nome para flatMap .

Herman Van Der Blom
fonte