共计 2603 个字符,预计需要花费 7 分钟才能阅读完成。
在项目的实际开发中偶然遇到了相同的 GET 请求被连续触发的问题,典型用例如 CMS 系统首页打开时导航栏需要加载栏目数据,页面中的栏目列表也同样请求该数据。当然,理想状态下可以要求导航栏先加载并缓存,然后其它组件从缓存中获取,然而实际上这些功能可能由不同的开发者编写,那么协调起来就麻烦一些了。而且越复杂的系统就更容易的出现这个问题,所以不得不解决一下了。最初遇到这个问题是在一个 AngularJS(AngularJS1.6 测试通过)项目中,所以先丢这个出来:
/**
* 这只是一个简单的例子,请自行扩展。
* 返回的值总是一个 promise,这样就默默的拦截了重复的请求
* 注意:这里使用了本地缓存,这可能造成数据无法更新,
* 而下一个例子则仅仅是过滤掉一个请求周期之内重复的请求
*/
function get(url) {
var defer = $q.defer();
if (localStage.getItem(‘cachedRequest-‘ + url) !== null) {
if (localStage.getItem(‘cachedRequest-‘ + url).then) {
//then 方法不是 undefined 那么这就是个 promise 对象,扔回去
return localStage.getItem(‘cachedRequest-‘ + url);
} else {
// 数据已经本地缓存了那就放到 defer 里面返回
defer.resolve(JSON.parse(localStage.getItem(‘cachedRequest-‘ + url)));
}
} else {
// 不好解释,要打太多字 … 明白就好
var promise = $http.get(url).then(function(res){
localStage.setItem(‘cachedRequest-‘ + url, JSON.stringify(res));
return defer.resolve(res);
});
defer.resolve(promise);
}
return defer.promise();
}
Angular 版本(Angular6 测试通过)
/**
* 这是最简代码,错误处理等是使用拦截器实现的
*/
import {Injectable} from ‘@angular/core’;
import {throwError, Subject} from ‘rxjs’;
import {HttpClient, HttpHeaders} from ‘@angular/common/http’;
import {map} from ‘rxjs/operators’;
// 如果配置文件中设置了代理那么可以丢掉这个
const BEServer = “http://localhost”;
@Injectable({
providedIn: ‘root’
})
export class ApiRequestService {
private apiSubjects = {};
constructor(
private http: HttpClient
) {
}
private extractData(res: Response) {
let body = res;
return body || {};
}
private buildUrl(url, params) {
/* 此处略去 N 行代码和迭代方法 */
return url;
}
private getHttpOptions(type) {
/* 各种略 */
return {headers:{}};
}
exec(type, url, data = null) {
url = BEServer + url;
let method = type.toLowerCase();
if (([‘get’, ‘post’, ‘put’, ‘delete’, ‘file’]).indexOf(method) < 0) {
return throwError(‘Request method is invalid.’);
}
let httpOptions = this.getHttpOptions(method);
if (method == ‘get’) {
if (data) {
url = this.buildUrl(url, data);
}
}
if (method == ‘get’) {
if (! this.apiSubjects[url]) {
this.apiSubjects[url] = {
subscribe: this.http[method](url, httpOptions).pipe(map(this.extractData)).subscribe(data => {
this.apiSubjects[url].subject.next(data);
// 这个 delete 的处理感觉不顺,但是实测也找不到更好的办法
delete(this.apiSubjects[url]);
}),
subject: new Subject<Object>()
};
}
return this.apiSubjects[url].subject;
} else if (method == ‘delete’) {
return this.http[method](url, httpOptions).pipe(map(this.extractData));
} else {
return this.http[method](url, data, httpOptions).pipe(map(this.extractData));
}
}
}
// 调用测试,不必要的代码全略掉
instanceOfApiRequestService.exec(‘GET’, ‘/api/dashboard’).subscribe(data => {
console.log(data);
});
instanceOfApiRequestService.exec(‘GET’, ‘/api/dashboard’).subscribe(data => {
console.log(data);
});
instanceOfApiRequestService.exec(‘GET’, ‘/api/dashboard’).subscribe(data => {
console.log(data);
});
// 三次 log 都被触发,但是只有一次 http 请求。
刚接触 Angular6 不久,不管是我这个想法本身有错误还是解决的方式有问题都请拍砖不要客气,只求大侠的砖头上绘制一下示例代码,不胜感激。