Handling 'too many requests' Responses
When doing it's first load the app makes a reasonable number of requests to the FreeAgent API which sometimes results in a 429 too many requests
error. Here I'll explain how I handle it.
The front-end and back-end of Freelance Insights both use axios
to handle AJAX calls from the front-end to the back-end and the back-end to the FreeAgent API. FreeAgent has a rate limit of 15 requests per minute, when you first launch the app it grabs the last year's worth of data which results in 3 or 4 API calls per month. Which means, depending on the speed these run at, we get a 429 too many requests
response every now and then. Obviously we need to pause and retry the request so we don't miss some data.
When returning 429 responses APIs often return a retry-after
header with a value in seconds to wait before trying again. So you don't just keep hammering it and repeatedly get more 429s back. I had a look into a few Axios add-on packages which make request retries easier but none seemed to allow you to make use of the retry-after
header as the pause time.
I then found a handy gist on GitHub (https://gist.github.com/edmondburnett/38ed3451de659dc43fa3f24befc0073b) which essentially did what I wanted with a few modifications.
It makes use of axios interceptors to catch all axios errors so we can check for a 429
response. If we get a 429
response then we introduce a pause based on the retry-after
header, then once it has elapsed we retry the request. Pretty simple, and requires no extra packages. Win-win.
The final source is below for anyone wanting to use it!
// forked gist: https://gist.github.com/Stuart98/222b8f5df2458b2994b4c51e954ff368
// original gist: https://gist.github.com/edmondburnett/38ed3451de659dc43fa3f24befc0073b
// returns a promise that resolves to the retried axios call, after the given number of milliseconds
const sleepRequest = (milliseconds, originalRequest) => {
return new Promise((resolve) => {
setTimeout(() => resolve(axios(originalRequest)), milliseconds);
});
};
axios.interceptors.response.use(undefined, (error) => {
const { config, response: { status, headers } } = error;
const originalRequest = config;
// if 429 then we retry
if (status === 429) {
// grab the `retry-after` value and parse to int
const retryAfter = parseInt(headers['retry-after'], 10);
// pause for retryAfter amount, passing in the original request
return sleepRequest(retryAfter * 1000, originalRequest);
}
return Promise.reject(error);
});