Skip to content

Instantly share code, notes, and snippets.

@itsprofcjs
Last active November 3, 2024 01:36
Show Gist options
  • Save itsprofcjs/1dbfe4603fb4ff897899a1b9d7977544 to your computer and use it in GitHub Desktop.
Save itsprofcjs/1dbfe4603fb4ff897899a1b9d7977544 to your computer and use it in GitHub Desktop.
Rxjs http fetch call with retry and backoff feature
import { fromFetch } from 'rxjs/fetch';
import { catchError, delay, mergeMap, retryWhen, switchMap } from 'rxjs/operators';
import { from, Observable, of, throwError } from 'rxjs';
const retryWithBackOff = (delayMs: number = 0, maxRetry: number = 0, backOffMs: number = 0) => {
let currentRetries = maxRetry;
return (source: Observable<any>) =>
source.pipe(
retryWhen((error: Observable<any>) =>
error.pipe(
mergeMap((error) => {
if (currentRetries-- > 0) {
const currentBackOffTime = delayMs + (maxRetry - currentRetries) * backOffMs;
return of(error).pipe(delay(currentBackOffTime));
}
return throwError(error);
})
)
)
);
};
/**
*
* @param error
* @param response
* Save response to check later for validating in apiErrorHandler.
* This is to know that we actually received the response from server.
* Which may be of status 400 and even 500.
* Thus ensuring that api is not failed by browser itself
*/
const mapErrorResponse = (error: any, response: Response) => {
let errorResponse = { data: error, response };
if (response.status >= 500) {
errorResponse.data = { data: null, message: 'We are facing some technical dificulty' };
}
return throwError(errorResponse);
};
/**
*
* To handle all type of error in api call.
* If mapErrorResponse() was called then we should receive this
* `{data : any , response : Response }` structure
* If response is undefined, then it means api call was not succesfully executed.
* It may be due to "NO_INTERNET", "REQUEST_TIMEOUT", error in creating request etc
*
*/
const apiErrorhandler = ({ data, response }: any = {}) => {
let sanitizedError;
if (!response) {
/**
*
* Its now confirmed that we have some browser error
*/
sanitizedError = { data: '', message: 'Error Reaching our server!' };
} else {
sanitizedError = data;
}
return throwError(sanitizedError);
};
const getResponseType = (response: Response) => {
const contentType = (response.headers.get('content-type') + '').toLocaleLowerCase();
let bodyType: 'json' | 'text' | 'raw';
if (contentType.indexOf('application/json') !== -1) {
bodyType = 'json';
} else if (contentType.indexOf('text/plain') !== -1) {
bodyType = 'text';
} else {
bodyType = 'raw';
}
return bodyType;
};
const getResponseBody = (response: Response) => {
const bodyType = getResponseType(response);
if (bodyType === 'json') {
return response.json();
} else if (bodyType === 'text') {
return response.text();
} else {
return response.blob();
}
};
/**
*
* Fetch api treats all the response from server as success.
* It means status code in range of 400,500 are treated as success.
* This is to treat such response as failure
* @param response
*/
const handleApiResponse = (response: Response, rawResponse: boolean = false) => {
if (response.ok && response.status >= 200 && response.status < 300) {
return rawResponse ? of(response) : getResponseBody(response);
} else {
if (rawResponse) {
return mapErrorResponse(response, response);
} else {
return from(getResponseBody(response)).pipe(
switchMap((body) => {
return mapErrorResponse(body, response);
})
);
}
}
};
interface RequestConfig extends RequestInit {
delayMs?: number;
maxRetry?: number;
backOffMs?: number;
rawResponse?: boolean;
}
const getApiAgent = (url: string | Request, init: RequestConfig) => {
const { delayMs, maxRetry, backOffMs, rawResponse, ...requestInit } = init;
return fromFetch(url, requestInit).pipe(
switchMap((response) => {
return handleApiResponse(response, rawResponse);
}),
retryWithBackOff(delayMs, maxRetry, backOffMs),
catchError(apiErrorhandler)
);
};
export const apiService = {
get(url: string | Request, init?: RequestConfig) {
const config = init ? { ...init, method: 'GET' } : { method: 'GET' };
return getApiAgent(url, config);
},
post(url: string | Request, init?: RequestConfig) {
const config = init ? { ...init, method: 'POST' } : { method: 'POST' };
return getApiAgent(url, config);
},
put(url: string | Request, init?: RequestConfig) {
const config = init ? { ...init, method: 'PUT' } : { method: 'PUT' };
return getApiAgent(url, config);
},
delete(url: string | Request, init?: RequestConfig) {
const config = init ? { ...init, method: 'DELETE' } : { method: 'DELETE' };
return getApiAgent(url, config);
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment