首页 > 解决方案 > 将嵌套的 forkjoins 返回为 observable

问题描述

我试图在我的解析器中返回一堆嵌套的 forkjoins 和普通订阅。为此,我尝试使用地图,但我认为我还没有完全掌握地图/switchMaps/mergeMaps 的概念。我知道代码还没有返回 UserResult,这是因为我还不知道如何将 questionAnswers 添加到 UserResult,但这对我当前的问题应该没有太大的区别。

我的目标是重写它,以便它返回一个可观察的。

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<UserResult> {
    const questionAnswers = Array<QuestionAnswer>();

    this.rs.getResult(this.auth.token, route.params['id']).subscribe(res => {
      forkJoin(
        this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
        this.accs.getAccount(res.userId)
      ).subscribe(results => {
        forkJoin(
          this.accs.getUserDetails(results[1].access_token),
          this.as.getAnswers(this.auth.token)
        ).subscribe(results2 => {
          results[0].forEach(cat => {
            this.cs
              .getQuestionsCategory(this.auth.token, cat.id)
              .subscribe(questions => {
                results2[1]
                  .filter(ans => ans.userId === results[1].uid)
                  .forEach(a => {
                    const question = questions.find(q => q.id === a.questionId);
                    if (!isNullOrUndefined(question)) {
                      const category = results[0].find(
                        c => c.id === a.categoryId
                      );
                      const qa = new QuestionAnswer(question, a);
                      qa.category = category.name;
                      questionAnswers.push(qa);
                    }
                  });
              });
          });
        });
      });
    });
}

我试着像这样重写它,但它根本不起作用。我收到了一些未定义的错误,但它们都指向管道的开头,没有什么特别的。

    const questionAnswers = Array<QuestionAnswer>();
    let res;
    let res2;

    return this.rs.getResult(this.auth.token, route.params['id']).pipe(
      map((res: Result) =>
        forkJoin(
          this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
          this.accs.getAccount(res.userId)
        )
      ),
      tap(results => (res = results)),
      map(results =>
        forkJoin(
          this.accs.getUserDetails(results[1].access_token),
          this.as.getAnswers(this.auth.token)
        )
      ),
      tap(results2 => (res2 = results2)),
      map(
        res[0]
          .forEach(cat => {
            this.cs.getQuestionsCategory(this.auth.token, cat.id);
          })
          .map(questions =>
            res2[1]
              .filter(ans => ans.userId === res[1].uid)
              .forEach(a => {
                const question = questions.find(q => q.id === a.questionId);
                if (!isNullOrUndefined(question)) {
                  const category = res[0].find(c => c.id === a.categoryId);
                  const qa = new QuestionAnswer(question, a);
                  qa.category = category.name;
                  questionAnswers.push(qa);
                }
              })
          )
      )
    );

编辑

我刚刚注意到,在点击 results2 后 res[0] 会导致

无法读取未定义的属性“0”

我认为这与我对水龙头的不当使用有关,因为它在我试图更改的订阅中运行良好。

编辑2

我将代码拆分为 Kurt 推荐的更小的函数,但是我不太确定如何将它与我用于类别的 forEach 一起使用。我也不知道应该在哪里创建我将作为可观察对象返回的最终对象


 getResultByRouteParamId(route: ActivatedRouteSnapshot): Observable<Result> {
    return this.rs.getResult(this.auth.token, route.params['id']);
  }

  forkJoinQuizCategoriesAndAccount(
    result: Result
  ): Observable<[Category[], Account]> {
    return forkJoin(
      this.quizs.getCategoriesQuiz(this.auth.token, result.quizId),
      this.accs.getAccount(result.userId)
    );
  }

  forkJoinUserDetailsAndAnswers(results: [Category[], Account]) {
    return forkJoin(
      this.accs.getUserDetails(results[1].access_token),
      this.as.getAnswers(this.auth.token)
    );
  }

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<UserResult> {

    const questionAnswers = Array<QuestionAnswer>();
    let result: Result;
    let res: [Category[], Account];
    let res2: [User, Answer[]];

    return this.getResultByRouteParamId(route).pipe(
      tap(resu => result = resu),
      switchMap((result: Result) => this.forkJoinQuizCategoriesAndAccount(result)),
      tap(results => (res = results)),
      switchMap(results => this.forkJoinUserDetailsAndAnswers(results)),
      tap(results2 => (res2 = results2)),
      switchMap(
          // Stuck here!
        res[0]
          .forEach(cat => {
            this.cs.getQuestionsCategory(this.auth.token, cat.id);
          })
          .map(questions =>
            res2[1]
              .filter(ans => ans.userId === res[1].uid)
              .forEach(a => {
                const question = questions.find(
                  (q: Question) => q.id === a.questionId
                );
                if (!isNullOrUndefined(question)) {
                  const category = res[0].find(
                    (c: Category) => c.id === a.categoryId
                  );
                  const qa = new QuestionAnswer(question, a);
                  qa.category = category.name;
                  questionAnswers.push(qa);
                }
              }
              // let ur = new UserResult(res2[1], result)
              // ur.questionAnswers = questionAnswers;
              // return ur;

              )
          )
      )
    );

标签: angularrxjsobservable

解决方案


所以......那是你到达那里的一大块 RxJS。

首先要做的事情——你不订阅 RxJS 操作符——你将 observables 链接在一起。

一些定义

switchMapconcatMap用于将一个 observable 的结果传递给另一个。

map用于将值从一种结构转换为另一种结构(类似于同名数组函数的概念)。

forkJoin组合多个可观察对象,并在它们全部完成时返回一个结果。

你的代码

在您开始尝试整理代码之前,我建议您考虑将每个步骤拆分为自己的功能。这将有望帮助您查看数据流并考虑您的依赖项在哪里。

我曾尝试将您的原始示例转换为 RxJS,但在考虑每个步骤实际试图实现的目标时有点迷茫。

我确实确定的是你最终会得到一个有点像这样的模式(我订阅这个演示的目的——你会返回 observable):

result: string;

ngOnInit() {
  this.initialValue().pipe(
    switchMap(result => this.forkJoinOne(result)),
    switchMap(result => this.forkJoinTwo(result)),
    switchMap(result => this.forkJoinThree(result)),
    map(result => this.mapFour(result))
  ).subscribe(result => {
    this.result = result;
  });
}

private initialValue(): Observable<string> {
  return of('zero');
}

private forkJoinOne(result: string): Observable<string[]> {
  return forkJoin([
    of(`${result} one`),
    of('four')
  ]);
}

private forkJoinTwo(results: string[]): Observable<string[]> {
  return forkJoin([
    of(`${results[0]} two`),
    of(`${results[1]} five`)
  ]);
}

private forkJoinThree(results: string[]): Observable<string[]> {
  return forkJoin([
    of(`${results[0]} three`),
    of(`${results[1]} six`)
  ]);
}

private mapFour(results: string[]): string {
  return results.join(' ');
}

每个可观察的步骤都被移到了它自己的函数中——这可以帮助你思考需要输入什么数据以及输出什么数据——你实际上是在每个步骤之间创建了一个契约。

switchMap只是采取一个可观察的并设置另一个。最后map是获取前一个 observable 的输出并将其转换为不同的值。

我在这里使用了字符串,希望可以轻松地跟踪数据流。我建议首先尝试理解我的简单示例,然后使用这些原则重新构建您的函数。

演示:https ://stackblitz.com/edit/angular-eedbqg

我的版本在以下方面与您的大致一致:

初始值

this.rs.getResult(this.auth.token, route.params['id'])

forkJoinOne

所有的叉连接都应该传入一个数组或一个对象。我更喜欢传入对象的相对较新的方式,它表示发出值的结构。(forkJoin({ a: myObs })返回{ a: value })。

forkJoin(
  this.quizs.getCategoriesQuiz(this.auth.token, res.quizId),
  this.accs.getAccount(res.userId)
)

forkJoinTwo

forkJoin(
  this.accs.getUserDetails(results[1].access_token),
  this.as.getAnswers(this.auth.token)
)

叉加入三

您需要将此循环转换为可观察的数组,并将其传递给forkJoin.

results[0].forEach(cat => {
  this.cs.getQuestionsCategory(this.auth.token, cat.id)

地图四

你需要整理你的地图。而不是forEach这里,更喜欢filtermap(数组函数)。

map(questions =>
  res2[1]
    .filter(ans => ans.userId === res[1].uid)
    .forEach(a => {
      const question = questions.find(q => q.id === a.questionId);
      if (!isNullOrUndefined(question)) {
        const category = res[0].find(c => c.id === a.categoryId);
        const qa = new QuestionAnswer(question, a);
        qa.category = category.name;
        questionAnswers.push(qa);
      }
    })

推荐阅读