UNPKG

3.05 kBJavaScriptView Raw
1import { noop } from './utils.js';
2const delay = (ms) => new Promise((resolve) => {
3 setTimeout(resolve, ms);
4});
5const defaultWait = (attempts) => {
6 return Math.pow(Math.SQRT2, attempts);
7};
8export default function rateLimit(instance, maxRetry = 5) {
9 const { responseLogger = noop, requestLogger = noop } = instance.defaults;
10 instance.interceptors.request.use(function (config) {
11 requestLogger(config);
12 return config;
13 }, function (error) {
14 requestLogger(error);
15 return Promise.reject(error);
16 });
17 instance.interceptors.response.use(function (response) {
18 // we don't need to do anything here
19 responseLogger(response);
20 return response;
21 }, async function (error) {
22 const { response } = error;
23 const { config } = error;
24 responseLogger(error);
25 // Do not retry if it is disabled or no request config exists (not an axios error)
26 if (!config || !instance.defaults.retryOnError) {
27 return Promise.reject(error);
28 }
29 // Retried already for max attempts
30 const doneAttempts = config.attempts || 1;
31 if (doneAttempts > maxRetry) {
32 error.attempts = config.attempts;
33 return Promise.reject(error);
34 }
35 let retryErrorType = null;
36 let wait = defaultWait(doneAttempts);
37 // Errors without response did not receive anything from the server
38 if (!response) {
39 retryErrorType = 'Connection';
40 }
41 else if (response.status >= 500 && response.status < 600) {
42 // 5** errors are server related
43 retryErrorType = `Server ${response.status}`;
44 }
45 else if (response.status === 429) {
46 // 429 errors are exceeded rate limit exceptions
47 retryErrorType = 'Rate limit';
48 // all headers are lowercased by axios https://github.com/mzabriskie/axios/issues/413
49 if (response.headers && error.response.headers['x-contentful-ratelimit-reset']) {
50 wait = response.headers['x-contentful-ratelimit-reset'];
51 }
52 }
53 if (retryErrorType) {
54 // convert to ms and add jitter
55 wait = Math.floor(wait * 1000 + Math.random() * 200 + 500);
56 instance.defaults.logHandler('warning', `${retryErrorType} error occurred. Waiting for ${wait} ms before retrying...`);
57 // increase attempts counter
58 config.attempts = doneAttempts + 1;
59 /* Somehow between the interceptor and retrying the request the httpAgent/httpsAgent gets transformed from an Agent-like object
60 to a regular object, causing failures on retries after rate limits. Removing these properties here fixes the error, but retry
61 requests still use the original http/httpsAgent property */
62 delete config.httpAgent;
63 delete config.httpsAgent;
64 return delay(wait).then(() => instance(config));
65 }
66 return Promise.reject(error);
67 });
68}