Angular 2 цепные запросы Http Get с итерируемым массивом

Ищу помощь с последовательностью Observable http, я хочу сделать два http-вызова API, второй зависит от первого. Первый возвращает массив URL-адресов, а второй выполняет запросы на получение каждого URL-адреса в этом массиве, а затем возвращает ответы в потоке. Если я жестко запрограммирую один из зависимых запросов, например, я верну один из заголовков, которые я ищу:

search(character): Observable<any> {
let that = this
let queryUrl: string = character.url;
return this.http.get(queryUrl)
  .map((response: Response) => {
    this.characterResults = response.json().films
    return this.characterResults
        //Example response: 
        // ["https://api.com/films/1", "https://api.com/films/2", "https://api.com/films/3", "https://api.com/films/4"]
  })
  .flatMap((film) => {
    return that.http.get(film[0])
    .map((response: Response) => {
      return response.json().title
    })
  })
}

getApiData(character) {
    this.apiService.search(character)
    .subscribe((results) => { // on sucesss
          console.log(results)
        },
        (err: any) => { // on error
          console.log(err);
        },
        () => { // on completion
          console.log('complete')
        }
      );

но любая попытка повторения использования forEach над этим массивом, а затем выполнение всех вызовов http дает мне эту ошибку:

browser_adapter.js:84 EXCEPTION: TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined

В идеале мне нужен элегантный способ выполнения этих последующих вызовов параллельно с массивом результатов первого вызова, но я не могу понять, какой из десятков методов rxJs мог бы помочь мне в этом. Может ли кто-нибудь предложить мне какую-либо помощь?


person C. Kearns    schedule 22.07.2016    source источник
comment
flatMap должен возвращать Observable, но ваш код ничего не возвращает. Может быть, это проблема.   -  person Harry Ninh    schedule 22.07.2016
comment
код был обновлен, вы были правы в этом   -  person C. Kearns    schedule 22.07.2016


Ответы (1)


Два способа: если вы хотите, чтобы каждый из результатов транслировался отдельно, тогда flatMap — ваш друг. Вы можете использовать его, чтобы сгладить что угодно, Arrays, Promises или Observables. В вашем случае вы можете связать запрос вместе, выполнив:

search(character): Observable<any> {
  return this.http.get(character.url)
    .flatMap((response: Response) => response.json().films)
    .flatMap((film: string) => this.http.get(film), 
             (_, resp) => resp.json().title)
}

getApiData(character) {
    this.apiService.search(character)
      .subscribe((results) => { // on sucesss
          //Called for each url result
          console.log(results)
        },
        (err: any) => { // on error
          console.log(err);
        },
        () => { // on completion
          console.log('complete')
        }
      );

Или вы можете использовать forkJoin вместо этого, чтобы вернуть массив результатов

return this.http.get(character.url)
  .flatMap((response: response) => 
             //Waits until all the requests are completed before emitting
             //an array of results
             Observable.forkJoin(response.json().files.map(film => 
               this.http.get(film).map(resp => resp.json().title)
             ));

Изменить 1

Чтобы более полно рассмотреть второй аргумент flatMap. Это функция, которая получает исходное значение, переданное первому методу селектора, в паре с результатом операции выравнивания. То есть, если селектор 1 получает a и возвращает Observable из [1, 2, 3]. Второй селектор будет вызываться 3 раза с аргументами (а, 1), (а, 2) и (а, 3).

Если вы хотите получить несколько значений, я бы посоветовал вам использовать вторичный оператор отображения, чтобы сохранить чистоту потока, но это зависит от вас.

.flatMap((film: string) => this.http.get(film),
         (film: string, resp: Response) => resp.json())
//Typescript's destructuring syntax, extracts the values you want
//and creates a new object with only those fields
.map(({date, title}) => ({date, title}));
person paulpdaniels    schedule 22.07.2016
comment
Благодарность! Первый случай подходит для моего варианта использования, он очень элегантный и работает. Я не уверен, что понимаю второй flatMap. .flatMap((film: string) => this.http.get(film), (_, resp) => resp.json().title) } что делает (_, resp)? И я не уверен, что понимаю, как вы сопоставляете http-возврат только с запятой, синтаксис меня смущает. - person C. Kearns; 22.07.2016
comment
flatMap принимает два аргумента, первый возвращает объект для сведения, а второй берет отдельные значения из источника сведения и применяет к ним сопоставление. В этом случае он берет ответы от каждого HTTP-вызова и извлекает заголовок из значения ответа. - person paulpdaniels; 22.07.2016
comment
Спасибо, Пол! Если бы я хотел извлечь заголовок и дату из объекта и вывести массив таких объектов, как: [ {title: 'resp.json().title', date: 'rsp.json().date'}, { title: 'title, date: 'date'}], чтобы я мог использовать *ngFor для отображения асинхронности в компоненте, возможно ли это? - person C. Kearns; 22.07.2016
comment
Это также дает мне два из каждого ответа - person C. Kearns; 22.07.2016
comment
и forkJoin выдает ошибку TypeError: Cannot read property 'Symbol(Symbol.iterator)' of undefined, которая у меня была раньше - person C. Kearns; 22.07.2016
comment
Если я просто сделаю return this.http.get(character.url) .flatMap((response: Response) => Observable.forkJoin(response.json()));, я не получу ошибку итератора, но в функции подписки будет возвращена ошибка неизвестного типа. - person C. Kearns; 22.07.2016
comment
Спасибо за отличные ответы Павел. Многому научился. - person C. Kearns; 23.07.2016