Почему нам нужно использовать flatMap?


94

Я начинаю использовать RxJS и не понимаю, почему в этом примере нам нужно использовать такую ​​функцию, как flatMapили concatAll; где здесь массив массивов?

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)})

Если кто-то сможет наглядно объяснить происходящее, это будет очень полезно.


1
этот ответ хорош из-за ценных ссылок, которые он предоставляет, но терминология rxjs плохо переводится на английский язык. (картинки лучше). Вот почему я рекомендую вместо этого запускать простые примеры, подобные этому, или более сложные примеры в репозитории rxjs и добавлять операторы «.do» до и после оператора flatmap и map, а затем просто устанавливать точку останова с помощью отладчика Chrome. вы сразу увидите, что каждый из них производит разный результат
— HipsterZipster

5
Думаю, если flatMapбы назвали mapThenFlatten, то было бы меньше путаницы.
— goat

Ответы:


73

Когда я начал смотреть, Rxjsя тоже наткнулся на этот камень. Мне помогло следующее:

  • документация с reactivex.io. Например, для flatMap: http://reactivex.io/documentation/operators/flatmap.html
  • документация от rxmarbles: http://rxmarbles.com/ . Вы не найдете flatMapтам, вы должны посмотреть mergeMapвместо этого (другое имя).
  • введение в Rx, которое вам не хватало: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 . Он обращается к очень похожему примеру. В частности, он касается того факта, что обещание сродни наблюдаемому, испускающему только одно значение.
  • наконец, глядя на информацию о типе из RxJava. Не набираемый Javascript здесь не помогает. В основном if Observable<T>обозначает наблюдаемый объект, который передает значения типа T, затем flatMapпринимает функцию типа в T' -> Observable<T>качестве аргумента и возвращает Observable<T>. mapпринимает функцию типа T' -> Tи возвращает Observable<T>.

    Возвращаясь к вашему примеру, у вас есть функция, которая производит обещания из строки URL. Итак T' : string, и T : promise. И из того, что мы сказали ранее promise : Observable<T''>, так что T : Observable<T''>с T'' : html. Если вы поместите эту функцию создания обещаний map, вы получите Observable<Observable<T''>>то, что вам нужно Observable<T''>: вы хотите, чтобы наблюдаемый объект испускал htmlзначения. flatMapназывается так, потому что он сглаживает (удаляет наблюдаемый слой) результат из map. В зависимости от вашего опыта, это может быть китайский язык, но мне все стало кристально ясно после ввода информации и рисунка отсюда: http://reactivex.io/documentation/operators/flatmap.html .


2
Я забыл упомянуть , что вы должны быть в состоянии упростить return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));в return jQuery.getJSON(requestUrl);качестве flatMapтакже принимает функцию селектора , которая возвращает обещание т.е. функцию типа T' -> Promise.
— user3743222 02

2
Вау, этот GitHub Gist ( gist.github.com/staltz/868e7e9bc2a7b8c1f754 ) чертовски фантастичен. Я рекомендую его всем, кто работает с любыми библиотеками ReactiveX, такими как RxJS.
— Джейкоб Штамм

@JacobStamm, я согласен. Просто упрощает жизнь.
— CruelEngine

Что делает этот синтаксис среднее: T’ -> T? Я понимаю, что Tэто общее, но что такое апостроф и не жирная стрелка?
— 1252748

Вы можете заменить T 'на X или Y, не меняя смысла нигде в ответе. Стрелка - это обозначение Haskell для сигнатуры типа. Итак, T '-> T - это подпись функции, которая принимает элемент типа T' и возвращает элемент типа T
— user3743222

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]]

Вы используете flatMap, когда у вас есть Observable, результаты которого больше Observable.

Если у вас есть наблюдаемое, которое создается другим наблюдаемым, вы не можете фильтровать, уменьшать или отображать его напрямую, потому что у вас есть Observable, а не данные. Если вы создаете наблюдаемое, выбирайте flatMap вместо map; тогда с тобой все в порядке.

Как и во втором фрагменте, если вы выполняете асинхронную операцию, вам необходимо использовать 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>


34

Люди склонны чрезмерно усложнять вещи , давая определение, которое гласит:

flatMap преобразует элементы, излучаемые Observable, в Observable, а затем сглаживает выбросы от них в один Observable

Клянусь, это определение до сих пор меня смущает, но я объясню его самым простым способом, используя пример

Наша ситуация : у нас есть наблюдаемый объект, который возвращает данные (простой URL-адрес), который мы собираемся использовать для выполнения HTTP-вызова, который вернет наблюдаемый объект, содержащий нужные нам данные, поэтому вы можете визуализировать ситуацию следующим образом:

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

поэтому, как вы можете видеть, мы не можем напрямую получить данные, которые нам нужны, поэтому первый способ получить данные мы можем использовать обычные подписки, например:

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

это работает, но, как вы можете видеть, мы должны вложить подписки, чтобы получить наши данные, в настоящее время это неплохо, но представьте, что у нас есть 10 вложенных подписок, обслуживание которых станет невозможным.

поэтому лучший способ справиться с этим - просто использовать оператор, flatMapкоторый будет делать то же самое, но заставляет нас избегать этой вложенной подписки:

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

32

flatMap преобразовать элементы, излучаемые Observable, в новые Observable, а затем сгладить выбросы из них в один Observable.

Ознакомьтесь с приведенным ниже сценарием, где get("posts")возвращается Observable, который "сглаживается" 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.

2
Хороший, простой ответ. Думаю, это может быть лучше всего.
— vaughan

«flatMap преобразует элементы, излучаемые Observable, в новые Observable, а затем сглаживает выбросы от них в один Observable». Это отличный материал.
— MBak

18

Просто:

[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]]

16

Это не массив массивов. Это наблюдаемое из наблюдаемых.

Следующее возвращает наблюдаемый поток строки.

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

Хотя это возвращает наблюдаемый поток наблюдаемого потока json

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

flatMap автоматически сглаживает наблюдаемое, чтобы мы могли непосредственно наблюдать за потоком json


3
Трудно понять эту концепцию, не могли бы вы добавить комментарии к визуальному элементу, который вы имеете в виду «возвращает наблюдаемый поток наблюдаемого потока json». Спасибо.
— user233232 02

@ user233232, например, от [x, x, x, x] до [[xxx], [[xxx], [xxx]]]]
— serkan

Ключом к пониманию первого предложения является понимание того, что flatMap(и map) не относятся к массивам. Эти операции можно определить в любом универсальном контейнере или оболочке, включая массивы, словари, «необязательные элементы», реактивные потоки, обещания, указатели и даже сами функции. Это новое свойство математической конструкции, называемой монадой. Все приведенные выше примеры соответствуют требованиям для монады, поэтому всем им можно дать определение mapи flatMap(с некоторыми оговорками).
— mklbtz

14

Здесь показана эквивалентная реализация flatMap с использованием подписок.

Без flatMap:

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

С помощью 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

Надеюсь, это поможет.

Оливье.


13

Observable - это объект, который генерирует поток событий: Next, Error и Completed.

Когда ваша функция возвращает Observable, она возвращает не поток, а экземпляр Observable. flatMapОператор просто переводит этот экземпляр в поток.

Это поведение по flatMapсравнению с map: Выполнить данную функцию и преобразовать полученный объект в поток.


7

С помощью 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)})

Без 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)})
})

0

flatMap преобразует элементы, излучаемые Observable, в Observable, а затем сглаживает выбросы от них в один Observable

Я не дурак, но мне пришлось прочитать это 10 раз, но до сих пор не понимаю. Когда я читаю фрагмент кода:

[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]

тогда я мог понять, что происходит, он делает две вещи:

flatMap :

  1. map : transform *) испускает элементы в Observables.
  2. плоский : затем объедините эти Observable как один Observable.

*) Слово преобразования говорит, что предмет может быть преобразован во что-нибудь еще.

Тогда становится ясно, что оператор слияния выполняет сглаживание без отображения. Почему бы не назвать это mergeMap ? Похоже, что для flatMap также существует Alias mergeMap с таким именем .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.