Angular/RxJS 6: How to prevent duplicate HTTP requests?

前端 未结 6 1532
自闭症患者
自闭症患者 2020-12-29 21:35

Currently have a scenario where a method within a shared service is used by multiple components. This method makes an HTTP call to an endpoint that will always have the same

6条回答
  •  无人及你
    2020-12-29 21:45

    Even though the solutions proposed by other before work, I find it annoying to have to manually create fields in each class for every different get/post/put/delete request.

    My solution is basically based on two ideas: a HttpService that manages all http requests, and a PendingService that manages which requests actually go through.

    The idea is to intercept not the request itself (I could have used an HttpInterceptor for that, but it would be too late because the different instances of the requests would have already been created) but the intention of making a request, before it's made.

    So basically, all requests go through this PendingService, which holds a Set of pending requests. If a request (identified by it's url) is not in that set, it means this request is new and we have to call the HttpClient method (through a callback) and save it as a pending request in our set, with it's url as key, and the request observable as the value.

    If later there's a request made to the same url, we check again in the set using its url, and if it's part of our pending set, it means... that is pending, so we return simply the observable we saved before.

    Whenever a pending request is finished, we call a method to delete it from the set.

    Here's an example assuming we're requesting... I don't know, chihuahas?

    This would be our little ChihuahasService:

    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { HttpService } from '_services/http.service';
    
    @Injectable({
        providedIn: 'root'
    })
    export class ChihuahuasService {
    
        private chihuahuas: Chihuahua[];
    
        constructor(private httpService: HttpService) {
        }
    
        public getChihuahuas(): Observable {
            return this.httpService.get('https://api.dogs.com/chihuahuas');
        }
    
        public postChihuahua(chihuahua: Chihuahua): Observable {
            return this.httpService.post('https://api.dogs.com/chihuahuas', chihuahua);
        }
    
    }
    

    Something like this would be the HttpService:

    import { HttpClient } from '@angular/common/http';
    import { Observable } from 'rxjs';
    import { share } from 'rxjs/internal/operators';
    import { PendingService } from 'pending.service';
    
    @Injectable({
        providedIn: 'root'
    })
    export class HttpService {
    
        constructor(private pendingService: PendingService,
                    private http: HttpClient) {
        }
    
        public get(url: string, options): Observable {
            return this.pendingService.intercept(url, this.http.get(url, options).pipe(share()));
        }
    
        public post(url: string, body: any, options): Observable {
            return this.pendingService.intercept(url, this.http.post(url, body, options)).pipe(share());
        }
    
        public put(url: string, body: any, options): Observable {
            return this.pendingService.intercept(url, this.http.put(url, body, options)).pipe(share());
        }
    
        public delete(url: string, options): Observable {
            return this.pendingService.intercept(url, this.http.delete(url, options)).pipe(share());
        }
    
    }
    

    And finally, the PendingService

    import { Injectable } from '@angular/core';
    import { Observable } from 'rxjs';
    import { tap } from 'rxjs/internal/operators';
    
    @Injectable()
    export class PendingService {
    
        private pending = new Map>();
    
        public intercept(url: string, request): Observable {
            const pendingRequestObservable = this.pending.get(url);
            return pendingRequestObservable ? pendingRequestObservable : this.sendRequest(url, request);
        }
    
        public sendRequest(url, request): Observable {
            this.pending.set(url, request);
            return request.pipe(tap(() => {
                this.pending.delete(url);
            }));
        }
    
    }
    
    

    This way, even if 6 different components are calling the ChihuahasService.getChihuahuas(), only one request would actually be made, and our dogs API won't complain.

    I'm sure it can be improved (and I welcome constructive feedback). Hope somebody finds this useful.

提交回复
热议问题