Skip to content

Commit 6444fba

Browse files
committed
Enable sharing a rate limiter
Between multiple axios clients, sharing the rate limiting between them.
1 parent 4d2fed8 commit 6444fba

File tree

4 files changed

+119
-17
lines changed

4 files changed

+119
-17
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,27 @@ http.getMaxRPS() // 3
3939
http.setRateLimitOptions({ maxRequests: 6, perMilliseconds: 150 }) // same options as constructor
4040
```
4141

42+
You can also share a rate limiter between axios instances
43+
44+
```javascript
45+
import axios from 'axios';
46+
import rateLimit, { getLimiter } from 'axios-rate-limit';
47+
48+
const rateLimiter = getLimiter({ maxRPS: 2 })
49+
50+
const http1 = rateLimiter.enabled(axios.create({baseUrl: 'http://example.com/api/v1/users/1'}))
51+
// another way of doing the same thing:
52+
const http2 = rateLimit(
53+
axios.create({baseUrl: 'http://example.com/api/v1/users/2'}),
54+
{ rateLimiter: rateLimiter }
55+
)
56+
57+
http1.get('/info.json') // will perform immediately
58+
http2.get('/info.json') // will perform immediately
59+
http1.get('/info.json') // will after one second from the first one
60+
61+
```
62+
4263
## Alternatives
4364

4465
Consider using Axios built-in [rate-limiting](https://www.npmjs.com/package/axios#user-content--rate-limiting) functionality.

__tests__/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,31 @@ it('not delay requests if requests are cancelled', async function () {
191191
expect(end - start).toBeLessThan(perMilliseconds * 2)
192192
expect(end - start).toBeGreaterThan(perMilliseconds)
193193
})
194+
195+
it('can share a limiter between multiple axios instances', async function () {
196+
function adapter (config) { return Promise.resolve(config) }
197+
198+
var limiter = axiosRateLimit.getLimiter({
199+
maxRequests: 2, perMilliseconds: 100
200+
})
201+
202+
var http1 = limiter.enable(axios.create({ adapter: adapter }))
203+
// another way of doing the same thing:
204+
var http2 = axiosRateLimit(
205+
axios.create({ adapter: adapter }), { rateLimiter: limiter }
206+
)
207+
208+
var onSuccess = sinon.spy()
209+
210+
var requests = []
211+
requests.push(http1.get('/users/1').then(onSuccess))
212+
requests.push(http1.get('/users/2').then(onSuccess))
213+
214+
requests.push(http2.get('/users/3').then(onSuccess))
215+
requests.push(http2.get('/users/4').then(onSuccess))
216+
217+
await delay(90)
218+
expect(onSuccess.callCount).toEqual(2)
219+
await Promise.all(requests)
220+
expect(onSuccess.callCount).toEqual(4)
221+
})

src/index.js

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
function AxiosRateLimit (axios) {
1+
function AxiosRateLimit (options) {
22
this.queue = []
33
this.timeslotRequests = 0
44

5-
this.interceptors = {
6-
request: null,
7-
response: null
8-
}
5+
this.interceptors = []
96

107
this.handleRequest = this.handleRequest.bind(this)
118
this.handleResponse = this.handleResponse.bind(this)
129

13-
this.enable(axios)
10+
this.setRateLimitOptions(options)
1411
}
1512

1613
AxiosRateLimit.prototype.getMaxRPS = function () {
@@ -43,14 +40,27 @@ AxiosRateLimit.prototype.enable = function (axios) {
4340
return Promise.reject(error)
4441
}
4542

46-
this.interceptors.request = axios.interceptors.request.use(
43+
var requestInterceptor = axios.interceptors.request.use(
4744
this.handleRequest,
4845
handleError
4946
)
50-
this.interceptors.response = axios.interceptors.response.use(
47+
var responseInterceptor = axios.interceptors.response.use(
5148
this.handleResponse,
5249
handleError
5350
)
51+
52+
this.interceptors.push({
53+
axios: axios,
54+
request: requestInterceptor,
55+
response: responseInterceptor
56+
})
57+
58+
axios.getQueue = this.getQueue.bind(this)
59+
axios.getMaxRPS = this.getMaxRPS.bind(this)
60+
axios.setMaxRPS = this.setMaxRPS.bind(this)
61+
axios.setRateLimitOptions = this.setRateLimitOptions.bind(this)
62+
63+
return axios
5464
}
5565

5666
/*
@@ -156,16 +166,33 @@ AxiosRateLimit.prototype.shift = function () {
156166
* @returns {Object} axios instance with interceptors added
157167
*/
158168
function axiosRateLimit (axios, options) {
159-
var rateLimitInstance = new AxiosRateLimit(axios)
160-
rateLimitInstance.setRateLimitOptions(options)
169+
var rateLimitInstance = options.rateLimiter || new AxiosRateLimit(options)
161170

162-
axios.getQueue = AxiosRateLimit.prototype.getQueue.bind(rateLimitInstance)
163-
axios.getMaxRPS = AxiosRateLimit.prototype.getMaxRPS.bind(rateLimitInstance)
164-
axios.setMaxRPS = AxiosRateLimit.prototype.setMaxRPS.bind(rateLimitInstance)
165-
axios.setRateLimitOptions = AxiosRateLimit.prototype.setRateLimitOptions
166-
.bind(rateLimitInstance)
171+
rateLimitInstance.enable(axios)
167172

168173
return axios
169174
}
170175

176+
/**
177+
* Create a new rate limiter instance. It can be shared between multiple axios instances.
178+
* The rate-limiting is shared between axios instances that are enabled with this rate limiter.
179+
*
180+
* @example
181+
* import rateLimit, { getLimiter } from 'axios-rate-limit';
182+
*
183+
* const limiter = getLimiter({ maxRequests: 2, perMilliseconds: 1000 })
184+
* // limit an axios instance with this rate limiter:
185+
* const http1 = limiter.enable(axios.create())
186+
* // another way of doing the same thing:
187+
* const http2 = rateLimit(axios.create(), { rateLimiter: limiter })
188+
*
189+
* @param {Object} options options for rate limit, same as for rateLimit()
190+
* @returns {Object} rate limiter instance
191+
*/
192+
function getLimiter (options) {
193+
return new AxiosRateLimit(options)
194+
}
195+
171196
module.exports = axiosRateLimit
197+
module.exports.AxiosRateLimiter = AxiosRateLimit
198+
module.exports.getLimiter = getLimiter

typings/index.d.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export type RateLimitRequestHandler = {
44
resolve: () => boolean
55
}
66

7-
export interface RateLimitedAxiosInstance extends AxiosInstance {
7+
export interface RateLimiter {
88
getQueue: () => RateLimitRequestHandler[],
99
getMaxRPS: () => number,
1010
setMaxRPS: (rps: number) => void,
@@ -17,12 +17,38 @@ export interface RateLimitedAxiosInstance extends AxiosInstance {
1717
// shift():any
1818
}
1919

20+
export interface RateLimitedAxiosInstance extends AxiosInstance, RateLimiter {};
21+
2022
export type rateLimitOptions = {
2123
maxRequests?: number,
2224
perMilliseconds?: number,
2325
maxRPS?: number
2426
};
2527

28+
export interface AxiosRateLimiter extends RateLimiter {};
29+
30+
export class AxiosRateLimiter implements RateLimiter {
31+
constructor(options: rateLimitOptions);
32+
}
33+
34+
/**
35+
* Create a new rate limiter instance. It can be shared between multiple axios instances.
36+
* The rate-limiting is shared between axios instances that are enabled with this rate limiter.
37+
*
38+
* @example
39+
* import rateLimit, { getLimiter } from 'axios-rate-limit';
40+
*
41+
* const limiter = getLimiter({ maxRequests: 2, perMilliseconds: 1000 })
42+
* // limit an axios instance with this rate limiter:
43+
* const http1 = limiter.enable(axios.create())
44+
* // another way of doing the same thing:
45+
* const http2 = rateLimit(axios.create(), { rateLimiter: limiter })
46+
*
47+
* @param {Object} options options for rate limit, same as for rateLimit()
48+
* @returns {Object} rate limiter instance
49+
*/
50+
export function getLimiter (options: rateLimitOptions): AxiosRateLimiter;
51+
2652
/**
2753
* Apply rate limit to axios instance.
2854
*
@@ -50,5 +76,5 @@ export type rateLimitOptions = {
5076
*/
5177
export default function axiosRateLimit(
5278
axiosInstance: AxiosInstance,
53-
options: rateLimitOptions
79+
options: rateLimitOptions & { rateLimiter?: AxiosRateLimiter }
5480
): RateLimitedAxiosInstance;

0 commit comments

Comments
 (0)