处理异步的三种方式
为了避免同步请求造成页面“假死”的现象,通常会使用异步的方式向服务器发送 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