使用 RxJS 處理多個 Http 請求

閱讀 Angular 6/RxJS 最新教程,請訪問 前端修仙之路

有時候進入某個頁面時,咱們須要從多個 API 地址獲取數據而後進行顯示。管理多個異步數據請求會比較困難,但咱們能夠藉助 Angular Http 服務和 RxJS 庫提供的功能來實現上述的功能。處理多個請求有多種方式,使用串行或並行的方式。前端

基礎知識

mergeMap

mergeMap 操做符用於從內部的 Observable 對象中獲取值,而後返回給父級流對象。typescript

  • 合併 Observable 對象 ( jsBin)
const source = Rx.Observable.of('Hello');
//map to inner observable and flatten
const example = source.mergeMap(val => Rx.Observable.of(`${val} World!`));

const subscribe = example.subscribe(val => console.log(val)); //output: 'Hello World!'

在上面示例中包含兩種 Observable 類型:json

  • 源 Observable 對象 - 即 source 對象
  • 內部 Observable 對象 - 即 Rx.Observable.of(`${val} World!`) 對象

僅當內部的 Observable 對象發出值後,纔會合併源 Observable 對象輸出的值,並最終輸出合併的值。api

forkJoin

forkJoin 是 Rx 版本的 Promise.all(),即表示等到全部的 Observable 都完成後,才一次性返回值。app

  • 合併多個 Observable 對象 (jsBin)
const getPostOne$ = Rx.Observable.timer(1000).mapTo({id: 1});
const getPostTwo$ = Rx.Observable.timer(2000).mapTo({id: 2});

Rx.Observable.forkJoin(getPostOne$, getPostTwo$).subscribe(
  res => console.log(res) // [{id: 1}, {id: 2}]
);

處理 Http 請求

咱們先來看一下 Angular Http 服務簡單示例。異步

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';

import 'rxjs/add/operator/map';

@Component({
  selector: 'app-root',
  template: `
    <p>HttpModule Demo</p>
  `
})
export class AppComponent implements OnInit {
  constructor(private http: Http) { }

  ngOnInit() {
    this.http.get('https://jsonplaceholder.typicode.com/users')
      .map(res => res.json())
      .subscribe(users => console.log(users));
  }
}

上面示例中,咱們經過依賴注入方式注入 http 服務,而後在 ngOnInit() 方法中調用 http 對象的 get() 方法來獲取數據。這個例子很簡單,它只處理一個請求,接下來咱們來看一下如何處理兩個請求。函數

Map 和 Subscribe

有些時候,當咱們發送下一個請求時,須要依賴於上一個請求的數據。即咱們在須要在上一個請求的回調函數中獲取相應數據,而後在發起另外一個 HTTP 請求。post

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Component({
  selector: 'app-root',
  template: `
    <p>{{username}} Detail Info</p>
    {{user | json}}
  `
})
export class AppComponent implements OnInit {
  constructor(private http: Http) { }

  apiUrl = 'https://jsonplaceholder.typicode.com/users';
  username: string = '';
  user: any;

  ngOnInit() {
    this.http.get(this.apiUrl)
      .map(res => res.json())
      .subscribe(users => {
        let username = users[6].username;
        this.http.get(`${this.apiUrl}?username=${username}`)
          .map(res => res.json())
          .subscribe(
            user => {
              this.username = username;
              this.user = user;
            });
      });
  }
}

在上面示例中,咱們先從 https://jsonplaceholder.typicode.com/users 地址獲取全部用戶的信息,而後再根據指定用戶的 username 進一步獲取用戶的詳細信息。雖然功能實現了,但有沒有更好的解決方案呢?答案是有的,能夠經過 RxJS 庫中提供的 mergeMap 操做符來優化上述的流程。jsonp

mergeMap

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';

@Component({
  selector: 'app-root',
  template: `
    <p>{{username}} Detail Info</p>
    {{user | json}}
  `
})
export class AppComponent implements OnInit {
  constructor(private http: Http) { }

  apiUrl = 'https://jsonplaceholder.typicode.com/users';

  username: string = '';

  user: any;

  ngOnInit() {
    this.http.get(this.apiUrl)
      .map(res => res.json())
      .mergeMap(users => {
        this.username = users[6].username;
        return this.http.get(`${this.apiUrl}?username=${this.username}`)
          .map(res => res.json())
      })
      .subscribe(user => this.user = user);
  }
}

在上面示例中,咱們經過 mergeMap 操做符,解決了嵌套訂閱的問題。最後咱們來看一下如何處理多個並行的 Http 請求。優化

forkJoin

接下來的示例,咱們將使用 forkJoin 操做符。若是你熟悉 Promises 的話,該操做符與 Promise.all() 實現的功能相似。forkJoin 操做符接收一個 Observable 對象列表,而後並行地執行它們。一旦列表的 Observable 對象都發出值後,forkJoin 操做符返回的 Observable 對象會發出新的值,即包含全部 Observable 對象輸出值的列表。具體示例以下:

import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/forkJoin';

@Component({
  selector: 'app-root',
  template: `
    <p>Post Detail Info</p>
    <ul>
      <li>{{post1 | json}}</li>
      <li>{{post2 | json}}</li>
    </ul>
  `
})
export class AppComponent implements OnInit {
  constructor(private http: Http) { }

  apiUrl = 'https://jsonplaceholder.typicode.com/posts';

  post1: any;

  post2: any;

  ngOnInit() {
    let post1 = this.http.get(`${this.apiUrl}/1`);
    let post2 = this.http.get(`${this.apiUrl}/2`);

    Observable.forkJoin([post1, post2])
      .subscribe(results => {
        this.post1 = results[0];
        this.post2 = results[1];
      });
  }
}

我有話說

除了 mergeMap 外,RxJS 中的 switchMap 有什麼用?

switchMap 操做符用於對源 Observable 對象發出的值,作映射處理。如有新的 Observable 對象出現,會在新的 Observable 對象發出新值後,退訂前一個未處理完的 Observable 對象。

使用示例:JSBin

var source = Rx.Observable.fromEvent(document.body, 'click');
var example = source.switchMap(e => Rx.Observable.interval(100).take(3));

example.subscribe({
    next: (value) => { console.log(value); },
    error: (err) => { console.log('Error: ' + err); },
    complete: () => { console.log('complete'); }
});

示例 marble 圖:

source : -----------c--c-----------------...
        concatMap(c => Rx.Observable.interval(100).take(3))
example: -------------0--0-1-2-----------...

以上代碼運行後,控制檯的輸出結果:

0
0
1
2

而在實際使用 Http 服務的場景中,好比實現 AutoComplete 功能,咱們能夠利用 switchMap 操做符,來取消無用的 Http 請求。

參考資源

相關文章
相關標籤/搜索