在 RxJS Observable 中“展平”数组的最佳方法

Posted

技术标签:

【中文标题】在 RxJS Observable 中“展平”数组的最佳方法【英文标题】:Best way to "flatten" an array inside an RxJS Observable 【发布时间】:2017-07-17 21:16:43 【问题描述】:

我的后端经常将数据作为一个 RxJS 5 Observable 中的数组返回(我使用的是 Angular 2)。

我经常发现自己想使用 RxJS 操作符单独处理数组项,我使用以下代码 (JSBin):

const dataFromBackend = Rx.Observable.of([
   name: 'item1', active: true ,
   name: 'item2', active: false ,
   name: 'item3', active: true 
]);

dataFromBackend
  // At this point, the obs emits a SINGLE array of items
  .do(items => console.log(items))
  // I flatten the array so that the obs emits each item INDIVIDUALLY
  .mergeMap(val => val)
  // At this point, the obs emits each item individually
  .do(item => console.log(item))
  // I can keep transforming each item using RxJS operators.
  // Most likely, I will project the item into another obs with mergeMap()
  .map(item => item.name)
  // When I'm done transforming the items, I gather them in a single array again
  .toArray()
  .subscribe();

mergeMap(val => val) 行感觉不是很地道。

有没有更好的方法将转换应用于由 Observable 发出的数组成员?

注意。我希望 RxJS 运算符(相对于数组方法)来转换我的项目,因为我需要能够将每个项目投影到第二个 observable 中。典型用例:项目ID列表的后端返回,我需要从后端请求所有这些项目。

【问题讨论】:

不知道为什么。但是调用任何 flatAll 运算符,如 mergeAll 或 concatAll (反正同步无关紧要)。将作为可观察值返回每个值。 【参考方案1】:

我也遇到过类似的问题:

以下

在每个项目上运行, 提取深度嵌套的对象 将服务器响应转换为返回一个数组 在模板中作为异步管道使用
    this.metaData = backendPostReq(body)
      .pipe(
        mergeMap(item => item),   // splits the array into individual objects
        pluck('metaData', 'dataPie', 'data'), // plucks deeply nested property
        toArray()  // converted back to an array for ag grid
      )

    <ag-grid-angular
        *ngIf="metaData | async as result"
        [gridOptions]="dataCollectionConfig"
        [rowData]="result"
        [modules]="modules"
        class="ag-theme-balham data-collection-grid"
    >
    </ag-grid-angular>

【讨论】:

【参考方案2】:

您可以不带任何参数使用concatAll()mergeAll()

dataFromBackend.pipe(
  tap(items => console.log(items)),
  mergeAll(), // or concatAll()
)

这(包括mergeMap)仅在 RxJS 5 中有效,因为它以相同的方式处理 Observable、数组、类数组对象、Promise 等。

最终你也可以这样做:

mergeMap(val => from(val).pipe(
  tap(item => console.log(item)),
  map(item => item.name),
)),
toArray(),

2019 年 1 月:针对 RxJS 6 更新

【讨论】:

谢谢,马丁。 concatAll() 会保留原始数组中项目的顺序而 mergeAll() 不会保留,这对吗? @AngularFrance 由于您正在解包一个数组,那么您使用哪一个并不重要,两者都会产生有序的项目。这仅在与异步操作一起使用时才相关,而不是这种情况。 您需要的只是map(vals =&gt; vals.map(item =&gt; item.name)。没有额外的 mergeMap, from, toArray 等等。如果您将每个值映射到 Observable,那就另当别论了:stackblitz.com/edit/rxjs-xzocbh?file=index.ts【参考方案3】:

Angular 6 注释。

如果您使用的是可管道操作符,则 do 称为 tap!

https://www.learnrxjs.io/operators/utility/do.html 这是一个例子。

// RxJS 
import  tap, map, of, mergeMap from 'rxjs/operators';

backendSource
  .pipe(
   tap(items => console.log(items)),
   mergeMap(item => item ),
   map(item => console.log(item.property))
  );

【讨论】:

另一个水龙头提示:错误和完成有两个额外的参数 - 所以你也可以记录这些【参考方案4】:

实际上,如果您在流中需要它,只需使用它:

.flatMap(items => of(...items))

【讨论】:

这与items =&gt; items 之间没有有效的区别。您可能会争辩说这在某种意义上更具“可读性”,但如果您不知道flatMap() 做了什么,那么两者都没有任何意义。它们都产生Observable&lt;T&gt;,其中 T 是数组类型。 请注意,现在它被称为mergeMap,而不是flatMap【参考方案5】:

如果是同步操作,我建议改用javascriptArray.map,它甚至可以节省一些性能:

const dataFromBackend = Rx.Observable.of([
   name: 'item1', active: true ,
   name: 'item2', active: false ,
   name: 'item3', active: true 
]);

dataFromBackend
  .map(items => items.map(item => item.name))
  .subscribe();

【讨论】:

就是这样,我经常发现自己将每个项目投影到第二个 observable 中。典型用例:后端返回项目 ID 列表,我需要从后端请求所有这些项目。我应该把它包括在我的问题中。 这确实对如何构建您的流产生了影响-但在这种情况下,您可能更喜欢@martin的回答-尽管我必须在这里发表个人(稍微无关)评论:它通常是一个糟糕的 REST 架构,首先请求一个 id 列表,然后在单个休息调用中分别获取它们的详细信息 - 这对任何服务器来说都是一种折磨,而且通常比简单地在一次第一次调用中检索所有数据需要更长的时间。跨度> 当然。但正如您所知,有时我们只是 API 的消费者,而不是它的创造者。就在最近,我使用 Gmail JavaScript API 遇到了我刚刚描述的用例(必须对键列表进行单独调用,然后对实体本身进行调用)。

以上是关于在 RxJS Observable 中“展平”数组的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

RxJS 将 observable 附加到 typescript 中的 observable 数组列表

如何在 Angular 4 中推送到数组的 Observable? RxJS

Rxjs Observable 数组在给定的开始和结束值之间进行选择

什么是使用 RxJS 在包含数组的 Observable 上迭代(并应用一些逻辑)的优雅解决方案?

如何在 RxJS 中停止 Observable 的间隔

我可以使用 rxjs 针对 id 的字符串数组过滤 Observable