【问题标题】:Using RxJS to build data from multiple API calls使用 RxJS 从多个 API 调用构建数据
【发布时间】:2021-01-24 19:24:41
【问题描述】:

我正在尝试更好地了解如何使用 RxJS 运算符来解决我遇到的特定问题。我实际上有两个问题,但它们足够相似。

我正在从 API 端点 /api/v3/folders/${folderId}/documents 获取一堆文档,并且我已经设置了具有执行该功能的服务,并处理所有身份验证等。

然而,这个文档对象数组没有有描述属性。为了获得描述,我需要在上一次调用的每个文档上调用 /api/v3/documents/${documentId}/。我的文档界面是这样的:

export interface Document {
  id: number;
  name: string;
  description: string;
}

我想我需要使用mergeMap 来等待并获取文档,一些如何将描述添加到我的Document 界面并返回整个内容,但是我无法获得最终结果。

getDocuments(folderId: number) {
    return this.docService.getDocuments(folderId, this.cookie).pipe(
      map(document => {
        document; // not entirely sure what to do here, does this 'document' carry over to mergeMap?
      }),
      mergeMap(document => {
        this.docService.getDocument(documentId, this.cookie)
      }) // possibly another map here to finalize the array? 
    ).subscribe(res => console.log(res));
  }

这可能看起来有点重复,但我发现的任何帖子都没有 100% 为我清除问题。

对于理解如何在第二次调用中正确使用数据并将其全部打包在一起的任何帮助,我们非常感谢非常。谢谢。

感谢@BizzyBob,这是带有编辑和解释的最终解决方案:

  getDocuments(folderId: number) {
    const headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'Context': this.cookie$
    });
    return this.docService.getDocuments(folderId, this.cookie$).pipe(
      mergeMap(documents => from(documents)),
      mergeMap(document => this.http.get<IDocument>(
        this.lcmsService.getBaseUrl() + `/api/v3/documents/${document.id}`,
        { headers: headers }
      ).pipe(
        map(completeDoc => ({...document, description: completeDoc.description}))
      )),
      toArray()
    ).subscribe(docs => {
      this.documents = docs;
      console.log(this.documents);
    }
    )
  }

由于某种原因,我无法从第二个服务订阅中拨打pipe(),所以我最终不得不在那里拨打http.get。错误是“不能在类型订阅上使用 pipe()”,这有点令人困惑,因为我在第一次订阅时使用了 pipe()。我将此更改应用于更新行为主题的服务功能,并且运行良好。谢谢!

【问题讨论】:

  • @fridoo 这确实有点帮助,谢谢
  • 是的,这是同一个问题。请注意,如果要保证最终 docs 数组中的项目顺序与 this.docService.getDocuments(folderId, this.cookie$) 发出的数组中项目的顺序相同,则必须使用 forkJoin 方法。
  • 啊,谢谢!我即将对实际数据进行更彻底的测试。希望一切正常。再次感谢!

标签: angular http rxjs observable mergemap


【解决方案1】:

由于您必须调用 document/{id} 端点来获取描述, 对于每个文档,您最终将作为您拥有的文档数量进行休息调用..

mergeMap 返回一个 observable 并在外部 observable 发射时订阅它。 (保持内部订阅与 switchMap 形成对比) https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap

Rxjs from 接受一个数组并返回一个 observable,按顺序发出数组中的每一项。 https://www.learnrxjs.io/learn-rxjs/operators/creation/from

所以我们可以同时使用这两种方法来实现您的目标:

抱歉,我使用手机时格式错误

我假设 docService.getDocuments 返回一个文档数组。

getDocuments(folderId: number) { 
    return this.docService.getDocuments(folderId, this.cookie).pipe(
      mergeMap((allDocumentsArray)=> {
             return from(allDocumentsArray).pipe(
               mergeMap((document) =>        
                   this.docservice.getDocument(document).pipe(
                        map(doc) => {...document,description})),//maps the document with document.description
     toArray(),//combines the returned values to an array
    ).subscribe(res => console.log(res));

我建议在他们的文档中阅读有关 mergemap 的内容。 并在使用我尚未测试的原因之前对此进行测试。

【讨论】:

  • 谢谢你,你的评论让我走上了正轨!
【解决方案2】:

有几种不同的方法可以组合来自多个 api 调用的数据。

我们可以:

  • 使用from 将每个项目单独发送到流中
  • 使用mergeMap订阅辅助服务调用
  • 在所有单独的调用完成后使用toArray 发出一个数组
  getDocuments() {
    return this.docService.getDocuments().pipe(
        mergeMap(basicDocs => from(basicDocs)),
        mergeMap(basicDoc => this.docService.getDocument(basicDoc.id).pipe(
          map(fullDoc => ({...basicDoc, description: fullDoc.description}))
        )),
        toArray()
    );
  } 

我们也可以使用forkJoin:

  getDocuments() {
    return this.docService.getDocuments().pipe(
      switchMap(basicDocs => forkJoin(
        basicDocs.map(doc => this.docService.getDocument(doc.id))
      ).pipe(
        map(fullDocs => fullDocs.map((fullDoc, i) => ({...basicDocs[i], description: fullDoc.description})))
      )),
    );
  }

这是一个有效的StackBlitz

此外,您可以考虑将流定义为变量documents$,而不是方法getDocuments()

  documents$ = this.docService.getDocuments().pipe(
    mergeMap(basicDocs => from(basicDocs))
    mergeMap(basicDoc => this.docService.getDocument(basicDoc.id).pipe(
      map(fullDoc => ({...basicDoc, description: fullDoc.description}))
    ))
    toArray()
  );

【讨论】:

  • 非常感谢!您对各个部分的解释对我很有帮助。 @coderrr22 让我走上了正确的道路,但你的回答最终为我巩固了一切。我从来没有使用过from(),我也从来没有得到过第二个pipe()map() 的权利。真的很感激。将使用最终解决方案编辑我的问题。
  • 另外,如果你有时间,你能解释一下为什么我不能在第二次订阅中使用pipe()而不是调用get而不是使用服务中的方法吗?
  • 第二次调用中无法访问第一次调用的数据的原因是它们不是同一个闭包的一部分。添加一个额外的“.pipe()”,将两个调用的结果放在同一个范围内,这样你就可以访问它们。查看这个答案(和问题)了解更多详情:stackoverflow.com/a/63462962/1858357
猜你喜欢
  • 1970-01-01
  • 2019-02-09
  • 2021-05-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-15
  • 2021-03-28
相关资源
最近更新 更多