为了避免同步请求造成页面“假死”的现象,通常会使用异步的方式向服务器发送 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 的特别之处在于提供了很多操作符,这些操作符可以将原始数据做过滤等处理,简化了异步处理的复杂度。
在 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 的强大之处在于操作符。
操作符本质上就是函数,对 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