1

在项目的实际开发中偶然遇到了相同的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]);
                        }, err => {
                            delete(this.apiSubjects[url]); //补上一句,不然会有bug
                        }), 
                        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不久,不管是我这个想法本身有错误还是解决的方式有问题都请拍砖不要客气,只求大侠的砖头上绘制一下示例代码,不胜感激。


incNick
6.3k 声望554 粉丝

普通的PHP开发工程师