Skip to content

Commit 694a73a

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

File tree

4 files changed

+113
-19
lines changed

4 files changed

+113
-19
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: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
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-
}
9-
105
this.handleRequest = this.handleRequest.bind(this)
116
this.handleResponse = this.handleResponse.bind(this)
127

13-
this.enable(axios)
8+
this.setRateLimitOptions(options)
149
}
1510

1611
AxiosRateLimit.prototype.getMaxRPS = function () {
@@ -43,14 +38,21 @@ AxiosRateLimit.prototype.enable = function (axios) {
4338
return Promise.reject(error)
4439
}
4540

46-
this.interceptors.request = axios.interceptors.request.use(
41+
axios.interceptors.request.use(
4742
this.handleRequest,
4843
handleError
4944
)
50-
this.interceptors.response = axios.interceptors.response.use(
45+
axios.interceptors.response.use(
5146
this.handleResponse,
5247
handleError
5348
)
49+
50+
axios.getQueue = this.getQueue.bind(this)
51+
axios.getMaxRPS = this.getMaxRPS.bind(this)
52+
axios.setMaxRPS = this.setMaxRPS.bind(this)
53+
axios.setRateLimitOptions = this.setRateLimitOptions.bind(this)
54+
55+
return axios
5456
}
5557

5658
/*
@@ -156,16 +158,33 @@ AxiosRateLimit.prototype.shift = function () {
156158
* @returns {Object} axios instance with interceptors added
157159
*/
158160
function axiosRateLimit (axios, options) {
159-
var rateLimitInstance = new AxiosRateLimit(axios)
160-
rateLimitInstance.setRateLimitOptions(options)
161+
var rateLimitInstance = options.rateLimiter || new AxiosRateLimit(options)
161162

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)
163+
rateLimitInstance.enable(axios)
167164

168165
return axios
169166
}
170167

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

typings/index.d.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,51 @@ 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,
1111
setRateLimitOptions: (options: rateLimitOptions) => void,
12-
// enable(axios: any): void,
1312
// handleRequest(request:any):any,
1413
// handleResponse(response: any): any,
1514
// push(requestHandler:any):any,
1615
// shiftInitial():any,
1716
// shift():any
1817
}
1918

19+
export interface RateLimitedAxiosInstance extends AxiosInstance, RateLimiter {};
20+
2021
export type rateLimitOptions = {
2122
maxRequests?: number,
2223
perMilliseconds?: number,
2324
maxRPS?: number
2425
};
2526

27+
export interface AxiosRateLimiter extends RateLimiter {};
28+
29+
export class AxiosRateLimiter implements RateLimiter {
30+
constructor(options: rateLimitOptions);
31+
enable(axios: AxiosInstance): RateLimitedAxiosInstance;
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)