使用 RxJS 从多个 API 调用构建数据

Using RxJS to build data from multiple API calls

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

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

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

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

我想我需要使用 mergeMap 等待并获取文档,了解如何将描述添加到我的 Document 界面和 return 整个过程中,但是我无法获得最终结果。

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

这可能看起来有点重复,但我发现的任何 post 都没有 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()。我将此更改应用于更新行为主题的服务功能,并且运行良好。谢谢!

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

mergeMap returns 一个 observable 并在外部 observable 发出时订阅它。 (与 switchMap 相比,保持内部订阅有效) https://www.learnrxjs.io/learn-rxjs/operators/transformation/mergemap

Rxjs from 采用一个数组和 returns 一个可观察对象,通过按顺序发射数组的每个项目。 https://www.learnrxjs.io/learn-rxjs/operators/creation/from

所以我们可以同时使用两者来实现您的目标:

(抱歉,我使用 phone 时格式不正确)

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

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 的内容。 并在使用之前测试一下,因为我还没有测试过。

有几种不同的方法可以从多个 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()
  );