Angular 2 верижни Http Get Requests с итерируем масив

Търся помощ за 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 пъти с аргументи (a, 1), (a, 2) и (a, 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((film: string) => this.http.get(film), (_, resp) => resp.json().title) } какво прави (_, resp)? И не съм сигурен, че разбирам как картографирате http return само със запетая, синтаксисът ме обърква. - 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 за показване на async в компонент, възможно ли е това? - 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