为了账号安全,请及时绑定邮箱和手机立即绑定

HTTP 服务与 RxJS

处理异步的三种方式

为了避免同步请求造成页面“假死”的现象,通常会使用异步的方式向服务器发送 HTTP 请求。在 Angular 中,处理异步操作主要有以下三种方式:

回调函数

回调函数是早期处理异步的方式,由于多层嵌套容易造成回调地狱,因此现在的编码中已经很少使用。

Promise

ES6 提供的处理异步的接口,在 Angular 中可以通过 toPromise() 方法把原来的 Observable 对象转换为 Promise 对象。

of(1).toPromise().then(data =>{
      console.log(data); // 输出:1
    });

RxJS

RxJS 是 Angular 内置的一套工具库,我们可以将 RxJS 看作是 Promise 的超集,能用 Promise 的场景 RxJS 也都适用,但是,RxJS 的特别之处在于提供了很多操作符,这些操作符可以将原始数据做过滤等处理,简化了异步处理的复杂度。

HTTP 服务与 RxJS

在 Angular 应用中,随处可见 RxJS,比如路由中的 params 返回一个 Observable 对象,表单中的 valueChanges 返回一个 Observable 对象,HTTP 服务中的 GET/POST/DELETE/PUT 方法都会返回一个 Observable 对象。

在 RxJS 中,Observable (可观察对象)是最为核心的概念,Angular 应用中产生的异步数据都会包装成 Observable 对象然后返回,它是数据的集合和源头,后续的所有操作都要围绕着 Observable 对象进行展开。

其实在 HTTP 服务中使用 RxJS 也很简单:调用 Observable 对象的 subscribe()方法订阅通知,一旦 HTTP 请求完成,Observable 对象就会向订阅者发送数据。

项目截图:

图片描述

例子:

服务器端的文件:

var express = require('express');
var app = express();

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "*");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("X-Powered-By",' 3.2.1')
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
 
app.get('/person', function (req, res) {
   res.send(JSON.stringify({"msg":"查询成功"}));
});
 
var server = app.listen(8081, function () {});

api.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

// 导入 Observable 对象
import { Observable } from 'rxjs';

@Injectable()
export class ApiService {

constructor(
  private http: HttpClient
  ) { }
  
  doGet(name: string) :Observable<any> {

    // GET 请求默认返回 Observable 对象
    return this.http.get(`http://localhost:8081/person?name=${name}`);
  }
  
  doPost(param: any) :Observable<any>  {

    // POST 请求默认返回 Observable 对象
    return this.http.post('http://localhost:8081/creat', param);
  }

}

app.component.ts

import { Component, OnInit } from '@angular/core';
import { ApiService } from 'src/app/common/service/api.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: [
    ApiService
  ],
})
export class AppComponent implements OnInit {

  constructor(
    private apiService: ApiService
  ){}

  ngOnInit(){

    // 调用 subscribe() 订阅 Observable 对象
    this.apiService.doGet('Tom').subscribe(
      {
        // 处理数据,不可省略
        next(value :any){
          console.log(value); // 输出: {msg: '查询成功'}
        },

        // 处理错误或异常,可以省略
        error(err :any){
          console.log(err);
        },

        // 当数据状态停止发生改变时的处理逻辑,可以省略
        complete(){
          console.log('End'); // 输出: End
        }
      }
      
      // 下面是更为简洁的写法:
      
      // (value :any)=>{
      //   console.log(value);
      // },
      // (error :any)=>{
      //   console.log(error);
      // },
      // ()=>{
      //   console.log('End');
      // }

    );
  }
}

RxJS 中的常用操作符

上面的例子只是 RxJS 的基本应用,而 RxJS 的强大之处在于操作符。
操作符本质上就是函数,对 Observable 对象进行转换、过滤、合并和监听之后再返回一个全新的 Observable 对象。
操作符种类繁多,我们这里只对常用的有关于 HTTP 服务的几种进行举例讲解。

管道操作符:pipe()

早期的操作符之间是通过链式调用的方式编写:

fromEvent(document, 'click').debounceTime(1000).take(5)

但是在实际工作中,很多操作符拼接在一起,代码的可读性较差,因此通过管道,既可以达到同样的效果,更便于阅读:

// 将需要使用的操作符 debounceTime 和 take 放置于管道中
fromEvent(document, 'click').pipe(
  debounceTime(1000),
  take(5)
).subscribe(value => console.log(value));

转换操作符:map()

将原 Observable 对象发出的数据转换成需要的数据。

例子:

服务器端的文件:

var express = require('express');
var app = express();

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "*");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("X-Powered-By",' 3.2.1')
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
 
app.get('/person', function (req, res) {
   res.send(JSON.stringify({"msg":"查询成功","data":req.query.name}));
});
 
var server = app.listen(8081, function () {});

观察服务器的返回值,其中,只有数据中的 data 字段是需要处理的,因此可以通过 map() 把原来的数据变成需要的数据。

app.component.ts

import { Component, OnInit } from '@angular/core';
import { ApiService } from 'src/app/common/service/api.service';

// 导入需要使用的操作符
import { pipe } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],

  providers: [
    ApiService
  ],
})
export class AppComponent implements OnInit {

  constructor(
    private apiService: ApiService
  ){}

  ngOnInit(){

    this.apiService.doGet('Tom').pipe(

      // 通过 map() 转换数据
      map((result: any)=>{
        return result.data;
      }
    )).subscribe((value: any)=>{
      console.log(value); // 输出:Tom
    });
  }

}

过滤操作符:filter()

过滤掉数据中不需要处理的数据,结果为 false 的数据将不会再向下流入。

例子:

服务器端的文件:

var express = require('express');
var app = express();

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "*");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("X-Powered-By",' 3.2.1')
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
 
app.get('/person', function (req, res) {
   res.send(JSON.stringify({"msg":"查询成功","data":""}));
});
 
var server = app.listen(8081, function () {});

观察服务器的返回值,数据中的 data 字段为空,因此可以通过 filter() 阻止数据流入下一个操作符 map()。

app.component.ts

import { Component, OnInit } from '@angular/core';
import { ApiService } from 'src/app/common/service/api.service';

// 导入需要使用的操作符
import { pipe } from 'rxjs';
import { map, filter } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],

  providers: [
    ApiService
  ],
})
export class AppComponent implements OnInit {

  constructor(
    private apiService: ApiService
  ){}

  ngOnInit(){

    this.apiService.doGet('Tom').pipe(

      // 通过 filter() 过滤空数据
      filter((result: any)=>{
        return result.data;
      }),
      map((result: any)=>{
        return result.data;
      }
    )).subscribe((value: any)=>{
      console.log(value);
    });
  }

}

组合操作符:forkJoin()

组合两个及两个以上的 HTTP 服务( 服务返回 Observable 或者 Promise 皆可以),并且在这些服务都成功取值之后,才进行合并处理。

例子:

服务器端的文件:

var express = require('express');
var app = express();

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "*");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("X-Powered-By",' 3.2.1')
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
 
app.get('/person', function (req, res) {
   res.send(JSON.stringify({"msg":"查询成功","data":req.query.name}));
});

app.post('/creat', function (req, res) {
  res.send(JSON.stringify({"msg":"新增成功"}));
});
 
var server = app.listen(8081, function () {});

app.component.ts

import { Component, OnInit } from '@angular/core';
import { ApiService } from 'src/app/common/service/api.service';

// 导入需要使用的操作符
import { pipe, forkJoin } from 'rxjs';
import { map, filter } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],

  providers: [
    ApiService
  ],
})
export class AppComponent implements OnInit {

  constructor(
    private apiService: ApiService
  ){}

  ngOnInit(){
  
    // 通过 forkJoin() 组合三个异步请求
    forkJoin([ 

      this.apiService.doGet('Tom'),
      this.apiService.doPost({name:'Tom'}),
      Promise.resolve(88)

    ]).subscribe((value: any)=>{
      console.log(value); 
      // 输出:[{msg:"查询成功", data:"Tom"}, { msg:"新增成功"}, 88]
    });

  }

}

上面的例子中,三个请求都是相互独立的,当三个请求数据全部到达后才开始合并,并且返回包含三个数据的数组。

转换操作符:concatMap()

如果某次请求需要依赖前一次请求的结果,此时可以使用 concatMap() 操作符。

例子:

app.component.ts

import { Component, OnInit } from '@angular/core';
import { ApiService } from 'src/app/common/service/api.service';

// 导入需要使用的操作符
import { pipe, forkJoin } from 'rxjs';
import { map, filter, concatMap } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],

  providers: [
    ApiService
  ],
})
export class AppComponent implements OnInit {

  constructor(
    private apiService: ApiService
  ){}

  ngOnInit(){

    this.apiService.doGet('Amy').pipe(
      map((result :any) => {
        return result.data;
      }),

      // doGet 请求完毕之后,紧接着发出 doPost 请求
      concatMap((result :any) => {
        const obj = {name :result};
        return this.apiService.doPost(obj);
      })
    ).subscribe((value: any)=>{
      console.log(value); // 输出:{msg: '新增成功'}
    })

  }

}

工具操作符:timeout()

当请求超过指定时间没有返回数据时,便抛出错误,会被处理错误的回调函数接收。

服务器端的文件:

var express = require('express');
var app = express();

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "*");
  res.header("Access-Control-Allow-Methods", "*");
  res.header("X-Powered-By",' 3.2.1')
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
 
app.get('/person', function (req, res) {
   setTimeout(()=>{
    res.send(JSON.stringify({"msg":"查询成功","data":req.query.name}));
   },4000);
});
 
var server = app.listen(8081, function () {});

观察服务器方法,数据在4秒之后返回。

app.component.ts

import { Component, OnInit } from '@angular/core';
import { ApiService } from 'src/app/common/service/api.service';

// 导入需要使用的操作符
import { pipe, forkJoin } from 'rxjs';
import { map, filter, concatMap, timeout } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],

  providers: [
    ApiService
  ],
})
export class AppComponent implements OnInit {

  constructor(
    private apiService: ApiService
  ){}

  ngOnInit(){

    this.apiService.doGet('Amy').pipe(

      // 设置请求超过3秒则报错
      timeout(3000),
      map((result :any) => {
        return result.data;
      })
    ).subscribe(
      (value: any)=>{
        console.log(value);
      },
      (err: any)=>{
        console.log('请求超时'); // 输出:请求超时
      }
    )

  }

}

end

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
1.4万
获赞与收藏
860

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消