Angular 4-处理迭代中的嵌套订阅的最佳方法是什么

时间:2019-02-06 03:42:22

标签: angular async-await rxjs

我正在尝试处理HTTP调用的订阅嵌套在迭代中的情况。

1)在第一个返回的observable中,我将获得一个URLS对象

2)我需要遍历这些URL并在这些URls上进行http调用

3)在第三步中,同样的情况,我将获得URL并执行与第二步相同的操作

4)最后,我需要将第三步获得的数据推送到最终对象。

let finaldata = [];
this.service1.getstep1Data().subscribe((data1) => {
  // data1 = { name : 'abc', urls : ['http://url1.com','http://url2.com',........,'http://urlz.com']};
  data1.urls.foreach((url) => {
    this.service2.getstep2Data(url).subscribe((data2) => {
      // data2 = { name : 'abc', urls : ['http://url1.com','http://url2.com',........,'http://urlz.com']};
      data2.urls.foreach((url) => {
        this.service3.getfinalData(url).subscribe((data) => {
          // data = ["one","two"...."xyz"]
          finaldata.push(data[0]);
        })
      })
    })
  })
})

现在的问题是在迭代中处理异步调用,这种调用不会彼此等待。

我想等待所有异步调用在第二步中完成,然后执行第三步,否则第三步我们没有URL来进行剩余调用

我知道迭代异步调用不是一个好习惯。

有人可以帮我实现最佳实践吗?

************预先感谢**************

4 个答案:

答案 0 :(得分:1)

您可以使用forkJoin执行多个http请求,然后等待所有请求完成。然后归结为以正确的方式映射到合并的结果,然后提取所需的数据。

import { of, Observable, forkJoin } from 'rxjs';
import { switchMap, concatMap, map, reduce } from 'rxjs/operators';

this.service1.getStep1Data()
  .pipe(
    // we execute multiple getSet2Data requests for each data1 and wait for each to complete
    switchMap(data1 => forkJoin(data1.urls.map(url => this.service2.getStep2Data(url)))),
    // we spread the data2 responses
    switchMap(data2s => of(...data2s)),
    // we execute multiple getfinalData requests for each data2 and emit the results in the
    // order of the data2 results
    concatMap(data2 => forkJoin(data2.urls.map(url => this.service3.getfinalData(url)))),
    // we map to the data we want from the finalData result
    map(data3perData2 => data3perData2.map(data3 => data3[0])),
    // we concatenate the results so that only one array gets emmited
    reduce((acc, data) => acc.concat(data))
  )
  .subscribe(finalData => this.doMyThing(finalData));

或者,您可以先将多个forkJoin的结果与另一个forkJoin合并,而不是先传播data2响应并减少后来的Observable。

this.service1.getStep1Data()
  .pipe(
    switchMap(data1 => forkJoin(data1.urls.map(url => this.service2.getStep2Data(url)))),
    // execute getfinalData for every url from data2 and wait for all results
    // do this for every data2 object 
    switchMap(data2s => forkJoin(data2s.map(data2 => forkJoin(data2.urls.map(url => this.service3.getfinalData(url)))))),
    // fullData will be string[][][], so we flatten that to string[] with the first elements
    // from data3
    map(fullData => [].concat(...fullData).map(data3 => data3[0]))
  )
  .subscribe(finalData => this.doMyThing(finalData));

最后的映射和归约取决于您希望最终输出的样子。

https://stackblitz.com/edit/angular-1ze4w4

答案 1 :(得分:0)

要对服务器RxJs运算符(例如mergeMap)进行多个HTTP调用,forkJoin将是处理这种情况的最佳方法。

下面的示例示例将对您有所帮助。

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { forkJoin } from "rxjs/observable/forkJoin";

@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  loadedCharacter: {};
  constructor(private http: HttpClient) { }

  ngOnInit() {
    let character = this.http.get('https://swapi.co/api/people/1');
    let characterHomeworld = this.http.get('http://swapi.co/api/planets/1');

    forkJoin([character, characterHomeworld]).subscribe(results => {
      // results[0] is our character
      // results[1] is our character homeworld
      results[0].homeworld = results[1];
      this.loadedCharacter = results[0];
    });
  }
}

答案 2 :(得分:0)

您可以使用RXJS forkjoin实现解决方案。

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  name = 'Angular';
  constructor(private appservice: AppService){
    this.appservice.getService1().subscribe(urls=>{
      this.appservice.getService2(urls).subscribe(finalArrayList=>{
        this.appservice.getService3(finalArrayList).subscribe();
      });
    });
  }

  ngOnInit(){

  }
}

创建了Stackblitz https://stackblitz.com/edit/angular-fkh7xr

答案 3 :(得分:0)

您可以使用Subject代替简单的观察者。主题将提供时间间隔更新,因此将有助于同步使用api数据。

stackblitz demo

this.api.subjectCreateUser1.subscribe((data1) => {
  if (data1) {
      this.api.subjectCreateUser2.subscribe((data2) => {
          if(data2) {
              this.api.subjectCreateUser3.subscribe((data3) => {
                   if (data3) {
                       console.log(data3);
                   }
              });
          }
      });
  }
});

和如下所示的api调用。...

return this.newHttp.post(this._baseApiUrl, this.data1)
 .subscribe(success => {
   console.log(success);
   this.subjectCreateUser1.next(success);
 }, error => {
   this.subjectCreateUser1.next(error);
});

希望这会有所帮助。