Вернуть вложенные вилки как наблюдаемые - PullRequest
0 голосов
/ 05 марта 2020

Я пытаюсь вернуть кучу вложенных разветвлений и обычных подписок в моем распознавателе. Для этого я попытался использовать карты, но, по-моему, я еще не полностью понял концепцию maps / switchMaps / mergeMaps. Я знаю, что код еще не возвращает UserResult, это потому, что я пока не знаю, как я добавлю questionAnswers к UserResult, но это не должно быть такой большой разницей для моей текущей проблемы.

My цель состоит в том, чтобы переписать это так, чтобы оно возвращало наблюдаемое.

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

Я пытался переписать его таким образом, но он не работает вообще. Я получаю некоторые неопределенные ошибки, но все они указывают на начало канала, ничего не указывает c.

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

РЕДАКТИРОВАТЬ

Это просто пришло к мое внимание, что res [0] после нажатия результатов2 вызывает

Невозможно прочитать свойство '0' из неопределенного

Я предполагаю, что это как-то связано с моим Неправильное использование метчиков, так как он работает нормально в подписках, которые я пытаюсь изменить.

EDIT2

Я разбил код на более мелкие функции, как рекомендовал Курт Однако я не совсем уверен, как использовать это с 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;

              )
          )
      )
    );

1 Ответ

2 голосов
/ 05 марта 2020

Итак ... это большая часть Rx JS, которую вы получили.

Перво-наперво - вы не подписываетесь внутри операторов Rx JS - вы объединяете наблюдаемые вместе.

Некоторые определения

switchMap и concatMap используются для передачи результатов одного наблюдаемого другому.

map предназначен для преобразования значения из одной структуры в другую (аналогично концепции функции массива с тем же именем).

forkJoin объединяет несколько наблюдаемых и возвращает один результат когда все они завершатся.

Ваш код

Прежде чем вы даже начнете пытаться исправить свой код, я бы рекомендовал подумать о разделении каждого шага на его собственную функцию. Надеюсь, это поможет вам увидеть поток данных и подумать о том, где находятся ваши зависимости.

У меня было go при преобразовании вашего исходного примера в Rx JS, но я немного растерялся, думая о что каждый шаг на самом деле пытался достичь.

Что я сделал, так это то, что вы получите шаблон, похожий на этот (я подписываюсь для целей этой демонстрации - вы бы вернули наблюдаемое):

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 берет вывод предыдущей наблюдаемой и преобразует его в другое значение.

Я использовал здесь строки, чтобы, надеюсь, упростить отслеживание потока данных. Я бы порекомендовал начать с попытки понять мой простой пример, а затем перестроить вашу функцию, используя принципы.

DEMO: https://stackblitz.com/edit/angular-eedbqg

Моя версия примерно выравнивается с вашими следующими способами:

initialValue

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

forkJoinOne

All 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

Вы будете необходимо преобразовать этот l oop в массив наблюдаемых и передать его в forkJoin.

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

mapFour

Вам потребуется разбери свою карту. Вместо forEach здесь предпочтительнее filter и map (функция массива).

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