Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable sharing a rate limiter between multiple axios clients #76

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;