Skip to content

Commit

Permalink
Enable sharing a rate limiter
Browse files Browse the repository at this point in the history
Between multiple axios clients, sharing the rate limiting between them.
  • Loading branch information
leorochael committed Jul 25, 2024
1 parent 4d2fed8 commit 694a73a
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 19 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ http.getMaxRPS() // 3
http.setRateLimitOptions({ maxRequests: 6, perMilliseconds: 150 }) // same options as constructor
```

You can also share a rate limiter between axios instances

```javascript
import axios from 'axios';
import rateLimit, { getLimiter } from 'axios-rate-limit';

const rateLimiter = getLimiter({ maxRPS: 2 })

const http1 = rateLimiter.enabled(axios.create({baseUrl: 'http://example.com/api/v1/users/1'}))
// another way of doing the same thing:
const http2 = rateLimit(
axios.create({baseUrl: 'http://example.com/api/v1/users/2'}),
{ rateLimiter: rateLimiter }
)

http1.get('/info.json') // will perform immediately
http2.get('/info.json') // will perform immediately
http1.get('/info.json') // will after one second from the first one

```

## Alternatives

Consider using Axios built-in [rate-limiting](https://www.npmjs.com/package/axios#user-content--rate-limiting) functionality.
28 changes: 28 additions & 0 deletions __tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,31 @@ it('not delay requests if requests are cancelled', async function () {
expect(end - start).toBeLessThan(perMilliseconds * 2)
expect(end - start).toBeGreaterThan(perMilliseconds)
})

it('can share a limiter between multiple axios instances', async function () {
function adapter (config) { return Promise.resolve(config) }

var limiter = axiosRateLimit.getLimiter({
maxRequests: 2, perMilliseconds: 100
})

var http1 = limiter.enable(axios.create({ adapter: adapter }))
// another way of doing the same thing:
var http2 = axiosRateLimit(
axios.create({ adapter: adapter }), { rateLimiter: limiter }
)

var onSuccess = sinon.spy()

var requests = []
requests.push(http1.get('/users/1').then(onSuccess))
requests.push(http1.get('/users/2').then(onSuccess))

requests.push(http2.get('/users/3').then(onSuccess))
requests.push(http2.get('/users/4').then(onSuccess))

await delay(90)
expect(onSuccess.callCount).toEqual(2)
await Promise.all(requests)
expect(onSuccess.callCount).toEqual(4)
})
51 changes: 35 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
function AxiosRateLimit (axios) {
function AxiosRateLimit (options) {
this.queue = []
this.timeslotRequests = 0

this.interceptors = {
request: null,
response: null
}

this.handleRequest = this.handleRequest.bind(this)
this.handleResponse = this.handleResponse.bind(this)

this.enable(axios)
this.setRateLimitOptions(options)
}

AxiosRateLimit.prototype.getMaxRPS = function () {
Expand Down Expand Up @@ -43,14 +38,21 @@ AxiosRateLimit.prototype.enable = function (axios) {
return Promise.reject(error)
}

this.interceptors.request = axios.interceptors.request.use(
axios.interceptors.request.use(
this.handleRequest,
handleError
)
this.interceptors.response = axios.interceptors.response.use(
axios.interceptors.response.use(
this.handleResponse,
handleError
)

axios.getQueue = this.getQueue.bind(this)
axios.getMaxRPS = this.getMaxRPS.bind(this)
axios.setMaxRPS = this.setMaxRPS.bind(this)
axios.setRateLimitOptions = this.setRateLimitOptions.bind(this)

return axios
}

/*
Expand Down Expand Up @@ -156,16 +158,33 @@ AxiosRateLimit.prototype.shift = function () {
* @returns {Object} axios instance with interceptors added
*/
function axiosRateLimit (axios, options) {
var rateLimitInstance = new AxiosRateLimit(axios)
rateLimitInstance.setRateLimitOptions(options)
var rateLimitInstance = options.rateLimiter || new AxiosRateLimit(options)

axios.getQueue = AxiosRateLimit.prototype.getQueue.bind(rateLimitInstance)
axios.getMaxRPS = AxiosRateLimit.prototype.getMaxRPS.bind(rateLimitInstance)
axios.setMaxRPS = AxiosRateLimit.prototype.setMaxRPS.bind(rateLimitInstance)
axios.setRateLimitOptions = AxiosRateLimit.prototype.setRateLimitOptions
.bind(rateLimitInstance)
rateLimitInstance.enable(axios)

return axios
}

/**
* Create a new rate limiter instance. It can be shared between multiple axios instances.
* The rate-limiting is shared between axios instances that are enabled with this rate limiter.
*
* @example
* import rateLimit, { getLimiter } from 'axios-rate-limit';
*
* const limiter = getLimiter({ maxRequests: 2, perMilliseconds: 1000 })
* // limit an axios instance with this rate limiter:
* const http1 = limiter.enable(axios.create())
* // another way of doing the same thing:
* const http2 = rateLimit(axios.create(), { rateLimiter: limiter })
*
* @param {Object} options options for rate limit, same as for rateLimit()
* @returns {Object} rate limiter instance
*/
function getLimiter (options) {
return new AxiosRateLimit(options)
}

module.exports = axiosRateLimit
module.exports.AxiosRateLimiter = AxiosRateLimit
module.exports.getLimiter = getLimiter
32 changes: 29 additions & 3 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,51 @@ export type RateLimitRequestHandler = {
resolve: () => boolean
}

export interface RateLimitedAxiosInstance extends AxiosInstance {
export interface RateLimiter {
getQueue: () => RateLimitRequestHandler[],
getMaxRPS: () => number,
setMaxRPS: (rps: number) => void,
setRateLimitOptions: (options: rateLimitOptions) => void,
// enable(axios: any): void,
// handleRequest(request:any):any,
// handleResponse(response: any): any,
// push(requestHandler:any):any,
// shiftInitial():any,
// shift():any
}

export interface RateLimitedAxiosInstance extends AxiosInstance, RateLimiter {};

export type rateLimitOptions = {
maxRequests?: number,
perMilliseconds?: number,
maxRPS?: number
};

export interface AxiosRateLimiter extends RateLimiter {};

export class AxiosRateLimiter implements RateLimiter {
constructor(options: rateLimitOptions);
enable(axios: AxiosInstance): RateLimitedAxiosInstance;
}

/**
* Create a new rate limiter instance. It can be shared between multiple axios instances.
* The rate-limiting is shared between axios instances that are enabled with this rate limiter.
*
* @example
* import rateLimit, { getLimiter } from 'axios-rate-limit';
*
* const limiter = getLimiter({ maxRequests: 2, perMilliseconds: 1000 })
* // limit an axios instance with this rate limiter:
* const http1 = limiter.enable(axios.create())
* // another way of doing the same thing:
* const http2 = rateLimit(axios.create(), { rateLimiter: limiter })
*
* @param {Object} options options for rate limit, same as for rateLimit()
* @returns {Object} rate limiter instance
*/
export function getLimiter (options: rateLimitOptions): AxiosRateLimiter;

/**
* Apply rate limit to axios instance.
*
Expand Down Expand Up @@ -50,5 +76,5 @@ export type rateLimitOptions = {
*/
export default function axiosRateLimit(
axiosInstance: AxiosInstance,
options: rateLimitOptions
options: rateLimitOptions & { rateLimiter?: AxiosRateLimiter }
): RateLimitedAxiosInstance;

0 comments on commit 694a73a

Please sign in to comment.