如何在使用订阅的嵌套 for 循环中重构 HttpRequests?

How to refactor HttpRequests in nested for-loops that use subscribe?

我们有一个与前端无关的 REST-API,这意味着它总是将 IRI 发送到它的嵌套资源。因此,要检索一些数据,您总是必须进行多次 http 调用(首先获取父资源,然后获取其子资源等) 所以每个国家都有一个链接条目列表。每个条目都链接到一个产品,该产品具有指向其类别资源的 IRI。

export interface Country {
  countryId: number;
  name: string;
  code: string;
  projects: string[]; //contains an array of IRIs for the project resources
  entries: string[];
}

export interface Entry {
  entryId: number,
  country: string,
  information: string,
  strategy: string,
  action: string,
  user: string,
  product: string,
  salesStatus1: string,
  salesStatus2: string,
  salesStatus3: string,
  salesStatus4: string,
  salesStatus5: string,

}

export interface Product {
  productId: number,
  productName: string,
  sharepointId: string,
  category: string,
  plmId: string,
  productStatus1: string,
  productStatus2: string,
  productStatus3: string,
  productStatus4: string,
  productStatus5: string,
  productComponents: string[]
}

export interface Category {
  categoryId: number,
  name: string,
  children: string[]
}
export class Node {
  children: Node[] = [];
  name: string;
  isProduct: boolean;
}

因此,为了使用显示导航树所需的所有数据,我编写了以下代码:

ngOnInit() {
    let nodes: Node[] = new Array<Node>();
    this.countriesService.getAll()
      .subscribe(
        (countries) => {
          for (let country of countries) {
            let countryNode = new Node();
            countryNode.name = country.name;
            countryNode.isProduct = false;
            for (let entryUrl of country.entries) {
              this.entriesService.getByUrl(entryUrl).subscribe(
                (entry) => {
                  this.productsService.getByUrl(entry.product).subscribe(
                    (product) => {
                      this.categoryService.getByUrl(product.category).subscribe(
                        (category) => {
                          let categoryNode = new Node();
                          categoryNode.name = category.name;
                          categoryNode.isProduct = true;
                          countryNode.children.push(categoryNode);
                          for (let childrenUrl of category.children) {
                            this.categoryService.getByUrl(childrenUrl).subscribe(
                              (childCategory) => {
                                let categoryChildNode = new Node();
                                categoryChildNode.name = childCategory.name;
                                categoryChildNode.isProduct = false;
                                categoryNode.children.push(categoryChildNode);
                              }
                            )
                          }
                        }
                      )
                    }
                  )
                }
              )
            }
            nodes.push(countryNode);
          }
          this.dataChange.next(nodes);
        }
      );
  }

然而,由于我是 Angular 和 rxjs 的新手,我在“等待”直到所有调用完成并且所有数据(因为它是异步的)都存在(因此导航树总是未命中)时遇到问题元素)。像这样链接也是一种丑陋和不好的做法。所以我想重构 rxjs-methods 的代码,但是我完全不知道如何开始使用它,因为在检索数据之后我必须再次迭代它以获得嵌套的资源 IRI,并且还必须为树导航创建 Node 对象。

你能给我一些关于如何重构代码的帮助吗?

不确定这是否有帮助,我将请求与 rxjs 运算符结合起来。

this.countriesService.getAll()
  .pipe(
    map((cs) => forkJoin(cs.map(c => {
      const countryNode = new Node();
      countryNode.name = c.name;
      countryNode.isProduct = false;

      return forkJoin(c.entries.map(entryUrl => this.entriesService.getByUrl(entryUrl)))
        .pipe(
          switchMap(entries => forkJoin(entries.map(entry => this.productsService.getByUrl(entry.product)))),
          switchMap(products => forkJoin(products.map(product => this.categoryService.getByUrl(product.category)))),
          switchMap(categories => forkJoin(categories.map(category => {
            const categoryNode = new Node();
            categoryNode.name = category.name;
            categoryNode.isProduct = true;

            return forkJoin(category.children.map(childrenUrl => this.categoryService.getByUrl(childrenUrl)))
              .pipe(
                map(cc => {
                  categoryNode.children = cc.map(childCategory => {
                    const categoryChildNode = new Node();
                    categoryChildNode.name = childCategory.name;
                    categoryChildNode.isProduct = false;
                    return categoryChildNode;
                  });
                  return categoryNode;
                })
              );
          }))),
          map(categoryNodes => {
            countryNode.children = categoryNodes;
            return countryNode;
          })
        );
    })))
  ).subscribe(countryNodes => {
    this.dataChange.next(countryNodes)
  });