为啥我们需要使用 flatMap?
Posted
技术标签:
【中文标题】为啥我们需要使用 flatMap?【英文标题】:Why do we need to use flatMap?为什么我们需要使用 flatMap? 【发布时间】:2016-02-01 23:26:30 【问题描述】:我开始使用 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))
如果有人可以直观地解释正在发生的事情,那将非常有帮助。
【问题讨论】:
这个答案很棒,因为它提供了有价值的参考资料,但是 rxjs 术语不能很好地翻译成英文。 (图片更好)。这就是为什么我建议改为运行像这样的简单示例,或者在 rxjs 存储库中运行更复杂的示例,并在 flatmap 和 map 运算符之前和之后添加“.do”运算符,然后只需使用 Chrome 调试器设置断点。你会立即看到每个产生不同的输出 我认为如果flatMap
被命名为mapThenFlatten
,那么它就不会那么混乱了。
我个人不喜欢这个例子。你为什么要订阅一个可观察的 url 字符串。当然,它会让你想到“rx”的方式,但就代码而言,我觉得不直观,除非你使用它的时间足够长,这样你就不会再质疑它了。但对我来说看起来有点矫枉过正。难怪人们难以理解。
【参考方案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]
然后我可以理解发生了什么,它做了两件事:
平面地图:
map:将 *) 发射的项目转换为 Observables。 flat:然后将这些 Observable 合并为一个 Observable。
*) 转换词表示该项目可以转换成其他东西。
然后 merge 运算符变得清晰,它在没有映射的情况下进行展平。为什么不叫它mergeMap? flatMap 似乎还有一个别名 mergeMap。
【讨论】:
【参考方案4】:当我开始看Rxjs
时,我也偶然发现了那块石头。对我有帮助的是:
flatMap
:http://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' : string
和T : 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 的要求,所以它们都可以被定义为 map
和 flatMap
(有一些注意事项)。【参考方案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
运算符只是将该实例映射到流。
这是flatMap
与map
相比的行为:执行给定的函数并将生成的对象展平为流。
【讨论】:
以上是关于为啥我们需要使用 flatMap?的主要内容,如果未能解决你的问题,请参考以下文章
2021年大数据常用语言Scala(二十三):函数式编程 扁平化映射 flatMap
为啥 flatMap 不将 Stream<Stream<SomeClass>> 扁平化到 SomeClass 而是扁平化到 Stream<SomeClass>?
谈谈Combine中一个有趣操作符flatMap的使用与陷阱
谈谈Combine中一个有趣操作符flatMap的使用与陷阱