为什么我们需要使用 flatMap?

Why do we need to use flatMap?

我开始使用 RxJS,我不明白为什么在这个例子中我们需要使用像 flatMapconcatAll 这样的函数;这里的数组数组在哪里?

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

如果有人能直观地解释发生了什么,那将非常有帮助。

这不是数组的数组。它是可观察对象的可观察对象。

以下returns一个可观察的字符串流。

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

While this returns an observable stream of observable stream of json

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

flatMap 自动为我们展平 observable 以便我们可以直接观察 json 流

当我开始看Rxjs时,我也偶然发现了那块石头。对我有帮助的是:

  • 来自 reactivex.io 的文档。例如,对于 flatMaphttp://reactivex.io/documentation/operators/flatmap.html
  • 来自 rxmarbles 的文档:http://rxmarbles.com/。您不会在那里找到 flatMap,您必须查看 mergeMap(另一个名称)。
  • 您遗漏的 Rx 简介:https://gist.github.com/staltz/868e7e9bc2a7b8c1f754。它解决了一个非常相似的例子。特别是它解决了这样一个事实,即 promise 类似于仅发出一个值的 observable。
  • 终于看到了来自RxJava的类型信息。 Javascript 不被打字在这里无济于事。基本上,如果 Observable<T> 表示一个压入类型 T 的值的可观察对象,那么 flatMap 将类型 T' -> Observable<T> 的函数作为其参数,并且 returns Observable<T>map 采用 T' -> T 和 returns Observable<T> 类型的函数。

    回到您的示例,您有一个从 url 字符串生成承诺的函数。所以 T' : string,和 T : promise。而从我们之前所说的promise : Observable<T''>,所以T : Observable<T''>,和T'' : html。如果你把这个 promise producing 函数放在 map 中,当你想要的是 Observable<T''> 时,你会得到 Observable<Observable<T''>>:你希望 observable 发出 html 值。 flatMap 之所以这样称呼是因为它使 map 的结果变平(删除了一个可观察层)。根据您的背景,这对您来说可能是中文,但是通过输入信息和此处的绘图,一切对我来说都变得 crystal 清楚了:http://reactivex.io/documentation/operators/flatmap.html.

Observable 是一个发出事件流的对象:Next、Error 和 Completed。

当你的函数 returns 一个 Observable 时,它​​返回的不是一个流,而是一个 Observable 的实例。 flatMap 运算符只是将该实例映射到流。

map 相比,这就是 flatMap 的行为:执行给定的函数并将生成的对象展平为流。

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

当你有一个 Observable 的结果是更多的 Observable 时,你可以使用 flatMap。

如果你有一个由另一个可观察对象产生的可观察对象,你不能直接过滤、减少或映射它,因为你有一个可观察对象而不是数据。如果您生成一个可观察对象,请选择 flatMap over 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>

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

没有平面地图

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

简单:

[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 将一个 Observable 发出的项目转换为新的 Observable,然后将这些发出的项目扁平化为单个 Observable。

查看下面的场景,其中 get("posts") returns 一个 Observable 是 "flattened" by 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.

人们倾向于把事情复杂化,给出的定义是:

flatMap transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable

我发誓这个定义仍然让我感到困惑,但我将以最简单的方式解释它,即使用示例


我们的简单示例

1- 我们有一个 Observable,它 return 是一个简单的 URL 字符串。

2- 我们必须使用那个 URL 进行第二个 HTTP 调用。

3- 第二个 HTTP 调用将 return 一个包含我们需要的数据的可观察对象。


所以我们可以想象这样的情况:

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

此处显示使用订阅的 flatMap 的等效实现。

没有平面地图:

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

使用平面地图:

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

希望对您有所帮助。

奥利维尔

flatMap transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable

我不傻,但必须读 10 遍才能理解。

Map 的工作方式类似于数组中每个项目的 for...each 并转换数组中的项目,但保持数组原样:

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

Flatmap 与 map 的作用相同,但也会“展平”数组:

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

flatMap :

  1. map: transform *) emitted items into Observables.
  2. flat: then merge those Observables as one Observable.

*) 转换词表示该项目可以转换成其他东西。

然后 merge 运算符变得清晰,它在没有映射的情况下进行展平。为什么不叫它mergeMap呢?似乎还有一个 Alias mergeMapflatMap.

同名

flatMap is used to flatten an array of arrays into a single array.

map simply converts one array to an other array. For example, suppose you have a list of person objects like this:

const friends = [
    {name: 'Dave', kids: ['Max', 'Jack']},
    {name: 'Max', kids: ['Sam', 'Alex', 'Megan']},
    {name: 'Jordan', kids: ['Mason', 'Cameron', 'Kaylin']}
];

但您真正需要的是一组人名(即字符串:[“Dave”、“Max”、“Jordan”])。要将此人数组 object 转换为字符串数组,您首先需要这样定义映射函数:

const mapFunction = p -> p.name;

然后,像这样使用 array.map:

const names = friends.map(mapFunction);

其中 return 个:

["Dave", "Max", "Jordan"]

flatMap 与 map 的相似之处在于您将一个数组转换为另一个数组。但是有一些细微的区别: 首先,map一般是一个one-to-one的东西。映射函数取一 object 入,return 取一 object 出:

p -> p.name

这意味着3个人object会产生3个名字。

另一方面,

flatMap 是 one-to-many 的东西。映射函数接受一个 object 而 returns 一个数组输出:

p -> p.kids

最终结果:3 人 objects 将产生 8 个孩子的名字。因此,这段代码:

const mapFunction = p -> p.kids;
const kidNames = friends.flatMap(mapFunction);

将return:

["Max", "Jack", "Sam", "Alex", "Megan", "Mason", "Cameron", "Kaylin"]