I have a function which is handling multiple APIs request for me and puts each request in Retry mode if failed. Now, in case one request is already in Retry loop and another instance of the same API call arrives, my function is not able to trace this and add the redundant API call in retry loop again.
Assuming i am placing a call to
/api/info/authors
What is happening
1stREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
2ndREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
What should happen,
1stREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
2ndREQ| [re0]/ (MERGE)
Following is my Service aling with my Retry function,
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { retryWhen, mergeMap, finalize, share, shareReplay } from 'rxjs/operators';
import { Observable, throwError, of, timer } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) { }
private apiUrl: string = 'http://localhost/api-slim-php/public/api';
public dataServerURL: string = 'http://localhost/';
/*
This function fetches all the info from API /info/{category}/{id}
category : author & id : '' or 1,2,3... or a,b,c...
category : form & id : '' or 1,2,3...
category : location & id : '' or 1,2,3...
category : school & id : '' or 1,2,3...
category : timeframe & id : '' or 1,2,3...
category : type & id : '' or 1,2,3...
*/
public getInfoAPI(category: string, id: string = "", page: string = "1", limit: string = "10") {
var callURL: string = '';
if (!!id.trim() && !isNaN(+id)) callURL = this.apiUrl + '/info/' + category + '/' + id;
else callURL = this.apiUrl + '/info/' + category;
return this.http.get(callURL, {
params: new HttpParams()
.set('page', page)
.set('limit', limit)
}).pipe(
retryWhen(genericRetryStrategy({ maxRetryAttempts: 5, scalingDuration: 1000 })),
shareReplay()
);
}
}
export const genericRetryStrategy = ({
maxRetryAttempts = 3,
scalingDuration = 1000,
excludedStatusCodes = []
}: {
maxRetryAttempts?: number,
scalingDuration?: number,
excludedStatusCodes?: number[]
} = {}) => (attempts: Observable<any>) => {
return attempts.pipe(
mergeMap((error, i) => {
const retryAttempt = i + 1;
// if maximum number of retries have been met
// or response is a status code we don't wish to retry, throw error
if (
retryAttempt > maxRetryAttempts ||
excludedStatusCodes.find(e => e === error.status)
) {
console.log(error);
return throwError(error);
}
console.log(
`Attempt ${retryAttempt}: retrying in ${retryAttempt *
scalingDuration}ms`
);
// retry after 1s, 2s, etc...
return timer(retryAttempt * scalingDuration);
}),
finalize(() => console.log('We are done!'))
);
};
Note:
Someone had suggested about shareReplay() and so I had tried implementing it, but it was not able to handle the same request made from two other components/sources.
Following should only be 6, instead they are 12 on rapid click of two buttons calling same API (scaling duration was 1000ms).
NOTE:
Kindly avoid using FLAGS it's the final nuke in my opinion.
