Вызов нескольких API и подписка на данные с помощью MergeMap

Мне нужно вызвать api, который вернет массив объектов. Затем мне нужно перебрать все объекты и использовать идентификатор каждого объекта, который мне нужен, чтобы вызвать другой api. Затем я планирую сохранить данные в массиве и подписаться, чтобы у меня был доступ к настраиваемому массиву, который содержит данные из обоих вызовов api. Однако у меня это не работает.

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

   this.apiService.getIntakeEvents(90430)
.pipe(mergeMap((res: any)=>{
  const allData =[];
  for(const item of res) {
    let courseInfo = this.apiService.getSpecificCourse(item.courseId)
    allData.push({...item, courseInfo})
  }
  console.log(allData)
  return allData;
}))
.subscribe(res=> console.log(res))

person special3220    schedule 13.04.2021    source источник


Ответы (3)


Нам нужно использовать пару операторов RXJS, чтобы получить то, что вы хотите.

  • Мы можем использовать mergeMap, чтобы сгладить внутреннюю наблюдаемую
  • Затем мы используем combineLatest, чтобы получить последний результат каждого вызова API.
  • Затем мы используем map для каждого отдельного вызова API, чтобы вернуть item и связанный с ним courseInfo
this.apiService
      .getIntakeEvents(90430)
      .pipe(
        mergeMap((items) => {
          return items.map((item) =>
            this.apiService.getSpecificCourse(item.courseId).pipe(
              map((courseInfo) => {
                return {
                  item,
                  courseInfo,
                };
              })
            )
          );
        })
      )
      .subscribe((res) => console.log(res));
person Dane Brouwer    schedule 13.04.2021
comment
switchMap не сглаживает внутреннюю наблюдаемую, а отменяет текущую активную внутреннюю наблюдаемую, когда излучает внешняя наблюдаемая. В этом случае, поскольку это вызов API (только одно излучение из внешнего наблюдаемого), не будет никакой разницы между любыми операторами сопоставления. - person Michael D; 13.04.2021

Вам нужно будет преобразовать

this.apiService.getSpecificCourse(item.courseId);

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

Итак, если у вас установлена ​​последняя версия rxjs. Ты можешь сделать:

let courseInfo = lastValueFrom(this.apiService.getSpecificCourse(item.courseId)).then((info) => info);

который, я считаю, должен работать

person Luke Weaver    schedule 13.04.2021

Вам нужно будет использовать комбинированный оператор, например forkJoin, combineLatest или zip, чтобы запускать несколько наблюдаемых параллельно. Учитывая, что это вызов API, я бы сказал, что forkJoin здесь больше подходит, так как он излучает только после того, как все источники будут завершены. Краткое различие между ними здесь.

Вам также нужно будет использовать Array#map для преобразования массива объектов в массив HTTP-вызовов. После этого вы можете использовать оператор RxJS map для каждого наблюдаемого источника, чтобы преобразовать его обратно в объект с измененным свойством courseInfo.

this.apiService.getIntakeEvents(90430).pipe(
  mergeMap((res: any) => 
    forkJoin(
      res.map(item => this.apiService.getSpecificCourse(item.courseId)).pipe(
        map(courseInfo => ({ ...item, courseInfo: courseInfo }))
      )
    )
  )
).subscribe(
  next: (res: any) => {
    console.log(res)
  },
  error: (error: any) => {
    // handle error
  }
);

Обновление: API вызывает несколько свойств в объекте

Для обновления нескольких свойств объекта из внешнего наблюдаемого вам, возможно, придется использовать несколько forkJoin, заключенных во внешний forkJoin. Это, безусловно, только один способ сделать это, и могут быть другие способы лучше.

Пример

this.apiService.getIntakeEvents(90430).pipe(
  mergeMap((res: any) => 
    forkJoin({
      courses: forkJoin(res.map(item => this.apiService.getSpecificCourse(item.courseId))),
      names: forkJoin(res.map(item => this.apiService.getSpecificName(item.courseId))),
      ages: forkJoin(res.map(item => this.apiService.getSpecificAge(item.courseId)))
    }).pipe(map(({courses, names, age}) => 
      res.map((item, index) => ({
        ...item,
        courseInfo: courses[index],
        name: names[index],
        age: ages[index]
      })
    ))
  )
).subscribe(
  next: (res: any) => {
    console.log(res)
  },
  error: (error: any) => {
    // handle error
  }
);
person Michael D    schedule 13.04.2021
comment
Спасибо. Это работает. Итак, скажем, если у меня есть еще несколько вызовов api, похожих на мой второй рассматриваемый вызов api, я делаю тот же канал (map (...)) внутри forkJoin? - person special3220; 13.04.2021
comment
Нет, pipe(map(...) должен был только добавить результат внутреннего вызова API к объекту из внешнего вызова API. Для дополнительных вызовов API необходимо ввести дополнительные forkJoin. - person Michael D; 13.04.2021
comment
Здесь я делаю один вызов API для всех элементов. Разве невозможно сделать два или три вызова API для всех элементов, сохранить возвращаемое значение из вызовов API в массиве и вернуть этот массив? res.map ((item: any) = ›this.apiService.getSpecificCourse (item.courseId) - person special3220; 13.04.2021
comment
@ special3220: Вопрос расплывчатый. Возможно, вы могли бы предоставить то, что пробовали до сих пор, аналогично тому, что было в исходном вопросе. Тем не менее я обновил ответ примером, который может решить вашу проблему, а может и не решить. - person Michael D; 13.04.2021
comment
Спасибо большое за вашу помощь. Я новичок в программировании и застрял с ним уже пару дней. Вы спаситель. - person special3220; 13.04.2021