@elastic.io/component-commons-library
Version:
Library for most common component development cases
98 lines (87 loc) • 3.83 kB
text/typescript
import axios, { AxiosResponse, AxiosInstance, AxiosRequestConfig } from 'axios';
import { getLogger } from '../logger/logger';
export interface RetryOptions {
retriesCount?: number; // values are validated with API_RETRIES_COUNT const below
requestTimeout?: number; // values are validated with API_REQUEST_TIMEOUT const below
}
export const API_RETRIES_COUNT = {
minValue: 0,
defaultValue: 3,
maxValue: 5
} as const;
const ENV_API_RETRIES_COUNT = process.env.API_RETRIES_COUNT ? parseInt(process.env.API_RETRIES_COUNT, 10) : API_RETRIES_COUNT.defaultValue;
export const API_REQUEST_TIMEOUT = {
minValue: 500,
defaultValue: 15000,
maxValue: 120000
} as const;
/**
* if values are higher or lower the limit - they'll be overwritten.
* returns valid values for RetryOptions
*/
export const getRetryOptions = (): RetryOptions => {
const ENV_API_REQUEST_TIMEOUT = process.env.API_REQUEST_TIMEOUT ? parseInt(process.env.API_REQUEST_TIMEOUT, 10) : API_REQUEST_TIMEOUT.defaultValue;
return {
retriesCount: (ENV_API_RETRIES_COUNT > API_RETRIES_COUNT.maxValue || ENV_API_RETRIES_COUNT < API_RETRIES_COUNT.minValue)
? API_RETRIES_COUNT.defaultValue
: ENV_API_RETRIES_COUNT,
requestTimeout: (ENV_API_REQUEST_TIMEOUT > API_REQUEST_TIMEOUT.maxValue || ENV_API_REQUEST_TIMEOUT < API_REQUEST_TIMEOUT.minValue)
? API_REQUEST_TIMEOUT.defaultValue
: ENV_API_REQUEST_TIMEOUT
};
};
export const exponentialDelay = (currentRetries: number) => {
const maxBackoff = 15000;
const delay = (2 ** currentRetries) * 100;
const randomSum = delay * 0.2 * Math.random(); // 0-20% of the delay
return Math.min(delay + randomSum, maxBackoff);
};
export const sleep = async (ms: number) => new Promise((resolve) => {
setTimeout(resolve, ms);
});
export const exponentialSleep = async (currentRetries: number) => sleep(exponentialDelay(currentRetries));
export const getErrMsg = (errResponse: AxiosResponse) => {
const statusText = errResponse?.statusText || 'unknown';
const status = errResponse?.status || 'unknown';
const data = errResponse?.data || 'no body found';
return `Got error "${statusText}", status - "${status}", body: ${JSON.stringify(data)}`;
};
export const axiosReqWithRetryOnServerError = async function (options: AxiosRequestConfig, axiosInstance: AxiosInstance = axios, logger = getLogger()) {
const { retriesCount, requestTimeout } = getRetryOptions();
let response: AxiosResponse;
let currentRetry = 0;
let error;
const loggerInstance = this?.logger || logger;
while (currentRetry < retriesCount) {
try {
response = await axiosInstance.request({
...options,
timeout: requestTimeout,
validateStatus: (status) => (status >= 200 && status < 300) || (status === 404 && this?.cfg?.doNotThrow404)
});
return response;
} catch (err) {
loggerInstance.error(err.response ? getErrMsg(err.response) : err.message);
error = err;
if (err.response?.status < 500) {
throw error;
}
loggerInstance.info(`URL: "${options.url}", method: ${options.method}, Error message: "${err.message}"`);
loggerInstance.info(`Request failed, retrying(${1 + currentRetry})`);
await exponentialSleep(currentRetry);
currentRetry++;
}
}
throw error;
};
export const getFacelessRetriesCount = () => {
const FACELESS_RETRIES_COUNT = {
minValue: 0,
defaultValue: 3,
maxValue: 5,
};
const ENV_FACELESS_RETRIES_COUNT = process.env.FACELESS_RETRIES_COUNT ? parseInt(process.env.FACELESS_RETRIES_COUNT, 10) : FACELESS_RETRIES_COUNT.defaultValue;
return (ENV_FACELESS_RETRIES_COUNT > FACELESS_RETRIES_COUNT.maxValue || ENV_FACELESS_RETRIES_COUNT < FACELESS_RETRIES_COUNT.minValue)
? FACELESS_RETRIES_COUNT.defaultValue
: ENV_FACELESS_RETRIES_COUNT;
};