【问题标题】:Return nested forkjoins as observable将嵌套的 forkjoins 返回为 observable
【发布时间】:2020-03-05 13:18:37
【问题描述】:

我试图在我的解析器中返回一堆嵌套的 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”

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

EDIT2

我像 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;

              )
          )
      )
    );

【问题讨论】:

  • Kurt 在下面的回答太棒了,只是想补充一下,你出错的主要原因是 .pipe(map(x =&gt; forkJoin([...])), ...) 几乎总是错误的并且是反模式。这里x 是可观察对象的内部,通常是一个普通对象,并且您映射到forkJoin 可观察对象而不是另一个对象。正如 Kurt 所说,更喜欢 .pipe(switchMap(x =&gt; forkJoin([...])), ...).pipe(concatMap(x =&gt; forkJoin([...])), ...)

标签: angular rxjs observable


【解决方案1】:

所以...你得到了相当多的 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 只是获取一个 observable 并设置另一个。最终的map 将前面的 observable 的输出转换成不同的值。

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

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

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

初始值

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

forkJoinOne

所有的 fork 连接都应该传入一个数组或一个对象。我更喜欢传入对象的相对较新的方式,它表明了发出值的结构。 (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)
)

forkJoinThree

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

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

ma​​pFour

您需要整理地图。这里不是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);
      }
    })

【讨论】:

  • 我目前正在按照你说的那样做,但我不确定如何在 forEach 继续,我会在我的帖子中添加我所拥有的。
  • this.cs.getQuestionsCategory(this.auth.token, cat.id); 是什么?
  • 它检索属于测验类别的问题。我使用该方法遍历属于测验的每个类别,这样我就不会遇到大量属于不同测验的问题。
  • 它返回什么?可观察?
  • 它返回一个Observable>,包含属于指定ID类别的问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-11
  • 1970-01-01
相关资源
最近更新 更多