【问题标题】:Why do we need to use flatMap?为什么我们需要使用 flatMap?
【发布时间】:2016-02-01 23:26:30
【问题描述】:

我开始使用 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)})

如果有人可以直观地解释正在发生的事情,那将非常有帮助。

【问题讨论】:

  • 这个答案很棒,因为它提供了有价值的参考资料,但是 rxjs 术语不能很好地翻译成英文。 (图片更好)。这就是为什么我建议改为运行像这样的简单示例,或者在 rxjs 存储库中运行更复杂的示例,并在 flatmap 和 map 运算符之前和之后添加“.do”运算符,然后只需使用 Chrome 调试器设置断点。你会立即看到每个产生不同的输出
  • 我认为如果flatMap 被命名为mapThenFlatten,那么它就不会那么混乱了。
  • 我个人不喜欢这个例子。你为什么要订阅一个可观察的 url 字符串。当然,它会让你想到“rx”的方式,但就代码而言,我觉得不直观,除非你使用它的时间足够长,这样你就不会再质疑它了。但对我来说看起来有点矫枉过正。难怪人们难以理解。

标签: javascript rxjs


【解决方案1】:

人们倾向于通过给出以下定义来使事情​​过于复杂

flatMap 将 Observable 发出的项目转换为 Observables,然后将这些排放量扁平化为一个单一的 可观察的

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


我们的简单示例

1- 我们有一个 observable,它返回一个简单的 URL 字符串。

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

3- 第二个 HTTP 调用将返回一个包含我们需要的数据的 observable。


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

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

【讨论】:

    【解决方案2】:

    flatMap 用于将数组数组展平为单个数组。

    map 只是将一个数组转换为另一个数组。例如,假设您有一个这样的人员对象列表:

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

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

    const mapFunction = p -> p.name;
    

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

    const names = friends.map(mapFunction);
    

    返回:

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

    flatMap 与 map 类似,因为您将一个数组转换为另一个数组。但是有一些细微的区别: 首先,map一般是一对一的东西。映射函数取一个对象,返回一个对象:

    p -> p.name
    

    这意味着 3 个人对象将产生 3 个名称。

    另一方面,

    flatMap 是一对多的东西。映射函数接受一个对象,但返回一个数组:

    p -> p.kids
    

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

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

    将返回:

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

    【讨论】:

      【解决方案3】:

      flatMap 将 Observable 发出的项目转换为 Observables, 然后将这些排放量扁平化为单个 Observable

      我不傻,但读了 10 遍还是没看懂。当我阅读代码 sn-p 时:

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

      然后我可以理解发生了什么,它做了两件事:

      平面地图

      1. ma​​p:将 *) 发射的项目转换为 Observables。
      2. flat:然后将这些 Observable 合并为一个 Observable。

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

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

      【讨论】:

        【解决方案4】:

        当我开始看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> 的函数作为其参数,并返回Observable<T>map 接受 T' -> T 类型的函数并返回 Observable<T>

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

        【讨论】:

        • 我忘了提到你应该能够将return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl)); 简化为return jQuery.getJSON(requestUrl);,因为flatMap 还接受一个选择器函数,该函数返回一个承诺,即T' -> Promise 类型的函数。
        • 哇,GitHub Gist (gist.github.com/staltz/868e7e9bc2a7b8c1f754) 真是太棒了。我向任何使用任何 ReactiveX 库(如 RxJS)的人推荐它。
        • @JacobStamm 我同意。只是让事情变得更容易。
        • 这个语法是什么意思:T’ -> T?我将T 理解为泛型,但撇号和非胖箭头是什么?
        • 您可以用 X 或 Y 替换 T' 而不会改变答案中任何地方的含义。箭头是类型签名的 Haskell 符号。所以 T' -> T 是一个函数的签名,它接受一个 T' 类型的元素并返回一个 T 类型的元素
        【解决方案5】:

        这里展示了使用订阅的 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

        希望对您有所帮助。

        奥利维尔。

        【讨论】:

        • 对于像我这样想知道为什么缺少管道的人,管道是从 rxjs 5.5 及更高版本开始使用的,但在此响应中可以看到,运算符在早期版本中与 . 结合使用。
        【解决方案6】:

        它不是数组数组。它是 observable 中的 observable。

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

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

        虽然这会返回一个可观察的 json 流的可观察流

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

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

        【讨论】:

        • 这个概念很难理解,能否请您将 cmets 添加到视觉中,您的意思是“返回可观察的 json 流的可观察流”。谢谢。
        • @user233232,如 [x,x,x,x] 到 [[xxx],[[xxx],[xxx]]]
        • 理解第一句话的关键是理解flatMap(和map)对于数组来说并不特殊。可以在任何通用容器或包装器上定义这些操作,包括数组、字典、“可选”、反应流、承诺、指针,甚至函数本身。这是称为 monad 的数学结构的一个涌现属性。上面所有的例子都满足成为 monad 的要求,所以它们都可以被定义为 mapflatMap(有一些注意事项)。
        【解决方案7】:

        flatMap 将 Observable 发射的项目转换为新的 Observable,然后将这些项目的发射扁平化为单个 Observable。

        查看下面的场景,get("posts") 返回一个被flatMap“展平”的 Observable。

        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 将 Observable 发射的项目转换为新的 Observable,然后将这些项目的发射扁平化为单个 Observable。"这是很棒的东西。
        【解决方案8】:
        ['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。

        如果你有一个由另一个 observable 生成的 observable,你不能直接过滤、减少或映射它,因为你有一个 Observable 而不是数据。如果你产生一个可观察的选择 flatMap 而不是 map;那你就没事了。

        和第二个 sn-p 一样,如果你在做异步操作,你需要使用 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>

        【讨论】:

          【解决方案9】:

          简单:

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

          【讨论】:

            【解决方案10】:

            使用平面地图

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

            【讨论】:

              【解决方案11】:

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

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

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

              【讨论】:

                猜你喜欢
                • 2016-03-18
                • 2020-06-07
                • 2019-06-09
                • 2011-07-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-06-18
                • 2017-02-26
                相关资源
                最近更新 更多