dynamic-throttled-queue 1.1.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,172 +1,155 @@
1
1
  # dynamic-throttled-queue
2
2
 
3
- This project was forked from [shaunpersad/throttled-queue](https://github.com/shaunpersad/throttled-queue)
3
+ Dynamically throttles arbitrary code to execute between a minimum and maximum number of times per interval. Best for making throttled API requests.
4
4
 
5
- Dynamically throttles arbitrary code to execute between a minuimum and maximum number of times per interval. Best for making throttled API requests.
6
-
7
- For example, making network calls to popular APIs such as Twitter is subject to rate limits. By wrapping all of your API calls in a throttle, it will automatically adjust your requests to be within the acceptable rate limits.
5
+ For example, making network calls to popular APIs such as Twitter is subject to rate limits. By wrapping all of your API calls in a throttle, it will automatically adjust your requests to be within the acceptable rate limits.
8
6
 
9
7
  Unlike the `throttle` functions of popular libraries like lodash and underscore, `dynamic-throttled-queue` will not prevent any executions. Instead, every execution is placed into a queue, which will be drained at the desired rate limit.
10
8
 
11
- ## Release Notes
12
- v1.1.1 - Add default for option object
13
-
14
- v1.1.0 - Adding Retry ability, if returning false, function will be added back to the master queue to be retired.
15
-
16
- v1.0.0 - Initial Release
9
+ Originally forked from [shaunpersad/throttled-queue](https://github.com/shaunpersad/throttled-queue).
17
10
 
18
11
  ## Installation
19
- Can be used in a Node.js environment, or directly in the browser.
20
- ### Node.js
21
- `npm install dynamic-throttled-queue`
22
- ### Browser
23
- `<script src="dynamic-throttled-queue.min.js"></script>`
24
-
25
- ##Options
26
-
27
- | Param | Type | Description |
28
- | ------ | ------------------- | ------------ |
29
- | min_rpi | <code>{number}</code> | Minimum requests per interval |
30
- | max_rpi | <code>[number=min_rpi]</code> | Maximum requests per interval |
31
- | interval | <code>{number}</code> | Number of milliseconds between each batch of requests |
32
- | evenly_spaced | <code>[boolean=true]</code> | If true requests will be distributed throughout the interval time |
33
- | errors\_per\_second | <code>[number=5]</code> | Number of errors per second before deciding to either increase or decrease the current rpi |
34
- | back_off | <code>[boolean=true]</code> | If true and we hit the errors_per_interval watermark, we will back off for 1 interval |
35
- | retry | <code>[number=0]</code> | If greater than 0, any failed callbacks, will be put back onto the queue to retry upto X times |
36
12
 
13
+ ```bash
14
+ pnpm add dynamic-throttled-queue
15
+ ```
37
16
 
38
17
  ## Usage
39
- 1) If in node.js, `require` the factory function:
40
-
41
- ```js
42
- var throttledQueue = require('dynamic-throttled-queue');
43
- ```
44
- Else, include it in a script tag in your browser and `throttledQueue` will be globally available.
45
18
 
46
- 2) Create an instance of a throttled queue by specifying the maximum number of requests as the first parameter,
47
- and the interval in milliseconds as the second:
19
+ ```ts
20
+ import { createThrottledQueue } from "dynamic-throttled-queue";
48
21
 
49
- ```js
50
- const throttle = throttledQueue({min_rpi:5, interval:1000}); // at most 5 requests per second.
51
- ```
52
- 3) Use the `throttle` instance as a function to enqueue actions:
22
+ const throttle = createThrottledQueue({ min_rpi: 5, interval: 1000 });
53
23
 
54
- ```js
55
- throttle(function() {
56
- // perform some type of activity in here.
24
+ throttle(() => {
25
+ // perform some type of activity in here.
57
26
  });
58
27
  ```
59
28
 
60
- ## Quick Examples
61
- ### Basic
62
- Rapidly assigning network calls to be run, but they will be limited to 1 request per second.
29
+ Callbacks can return `false` to signal an error (used for dynamic rate adjustment and retry). Async callbacks (returning a Promise) are also supported — rejections and `false` resolutions count as errors.
63
30
 
64
- ```js
65
- var throttledQueue = require('dynamic-throttled-queue');
66
- var throttle = throttledQueue({min_rpi:1, interval:1000}); // at most make 1 request every second.
31
+ ## Options
67
32
 
68
- for (let i = 0; i < 100; i++) {
33
+ | Param | Type | Default | Description |
34
+ | ----- | ---- | ------- | ----------- |
35
+ | `min_rpi` | `number` | *required* | Minimum requests per interval |
36
+ | `max_rpi` | `number` | `min_rpi` | Maximum requests per interval |
37
+ | `interval` | `number` | *required* | Milliseconds between each batch of requests |
38
+ | `evenly_spaced` | `boolean` | `true` | Distribute requests evenly throughout the interval |
39
+ | `errors_per_interval` | `number` | `5` | Error threshold per interval before adjusting rate |
40
+ | `back_off` | `boolean` | `false` | Back off for 1 full interval when error threshold is hit |
41
+ | `retry` | `number` | `0` | Number of times to retry failed callbacks |
42
+ | `compact_threshold` | `number` | `512` | Minimum dead slots before internal queue compaction triggers |
43
+ | `onRateChange` | `(rate: number) => void` | — | Called when the current rate changes |
69
44
 
70
- throttle(function() {
71
- // make a network request.
72
- fetch('https://api.github.com/search/users?q=adrianbrowning').then(console.log);
73
- });
74
- }
75
- ```
76
- ### Reusable
77
- Wherever the `throttle` instance is used, your action will be placed into the same queue,
78
- and be subject to the same rate limits.
45
+ ## Handle API
79
46
 
80
- ```js
81
- const throttledQueue = require('dynamic-throttled-queue');
82
- const throttle = throttledQueue({min_rpi:1, interval:60 * 1000}); // at most make 1 request every minute.
47
+ `createThrottledQueue` returns a function with additional properties:
83
48
 
84
- for (let x = 0; x < 50; x++) {
49
+ | Property | Type | Description |
50
+ | -------- | ---- | ----------- |
51
+ | `stop()` | `() => void` | Stop processing the queue immediately |
52
+ | `pending` | `number` (readonly) | Number of callbacks still waiting in the queue |
85
53
 
86
- throttle(function() {
87
- // make a network request.
88
- fetch('https://api.github.com/search/users?q=adrianbrowning').then(console.log);
89
- });
90
- }
91
- for (let y = 0; y < 50; y++) {
54
+ ```ts
55
+ const throttle = createThrottledQueue({ min_rpi: 5, interval: 1000 });
92
56
 
93
- throttle(function() {
94
- // make another type of network request.
95
- fetch('https://api.github.com/search/repositories?q=throttled-queue+user:adrianbrowning').then(console.log);
96
- });
97
- }
57
+ throttle(() => fetch("/api/data"));
58
+ console.log(throttle.pending); // number of queued callbacks
59
+
60
+ throttle.stop(); // halt processing
98
61
  ```
99
- ### Bursts
100
- By specifying a number higher than 1 for the min_rpi, and setting `evenly_spaced: false` you can dequeue multiple actions within the given interval:
101
62
 
102
- ```js
103
- var throttledQueue = require('dynamic-throttled-queue');
104
- var throttle = throttledQueue({min_rpi:10, interval:1000, evenly_spaced: true}); // at most make 10 requests every second.
63
+ ## Examples
105
64
 
106
- for (let x = 0; x < 100; x++) {
65
+ ### Basic
66
+
67
+ ```ts
68
+ import { createThrottledQueue } from "dynamic-throttled-queue";
107
69
 
108
- throttle(function() {
109
- // This will fire at most 10 a second, as rapidly as possible.
110
- fetch('https://api.github.com/search/users?q=adrianbrowning').then(console.log);
111
- });
70
+ const throttle = createThrottledQueue({ min_rpi: 1, interval: 1000 });
71
+
72
+ for (let i = 0; i < 100; i++) {
73
+ throttle(() => {
74
+ fetch("https://api.example.com/data").then(console.log);
75
+ });
112
76
  }
113
77
  ```
114
- ### Evenly spaced
115
- By default your actions are evenly distributed over the interval `evenly_spaced: true`:
116
78
 
117
- ```js
118
- const throttledQueue = require('dynamic-throttled-queue');
119
- const throttle = throttledQueue({min_rpi: 10, interval: 1000, evenly_spaced:true}); // at most make 10 requests every second, but evenly spaced.
79
+ ### Batch mode (not evenly spaced)
120
80
 
121
- for (let x = 0; x < 100; x++) {
81
+ ```ts
82
+ const throttle = createThrottledQueue({ min_rpi: 10, interval: 1000, evenly_spaced: false });
122
83
 
123
- throttle(function() {
124
- // This will fire at most 10 requests a second, spacing them out instead of in a burst.
125
- fetch('https://api.github.com/search/users?q=adrianbrowning').then(console.log);
126
- });
84
+ for (let i = 0; i < 100; i++) {
85
+ throttle(() => {
86
+ // Fires up to 10 at once per second
87
+ fetch("https://api.example.com/data").then(console.log);
88
+ });
127
89
  }
128
90
  ```
129
91
 
130
- ### Min & Max Requests Per Interval
131
- By suppling a `min_rpi` & `max_rpi` value to the options object, you will be able to have a dynamically adjusting queue. This works by the function passed to `throttle` returning `false` if there was an issue. The starting requests per interval is as close to halfway bewteen the `min_rpi` and `max_rpi`, rounded to the nearest whole number.
132
-
133
- The second part of this is the `errors_per_second` option. This is set by default to 5 errors per second. Every X seconds, a check is made to see how many errors we have seen (through the use of `return false`) and if we see X or more, then the current requests per interval will decrease until we hit the `min_rpi` value. If between 0 - X errors are seen then we keep with the current requests per interval as is. Finally if there are 0 errors in the last check period then we will increase the current requests per interval until we reach `max_rpi`.
92
+ ### Dynamic rate adjustment
134
93
 
135
- ```js
136
- const throttledQueue = require('dynamic-throttled-queue');
137
- const throttle = throttledQueue({min_rpi: 1, max_rpi:5, interval: 1000}); // at most make 5 requests every second.
94
+ When `max_rpi` > `min_rpi`, the queue dynamically adjusts throughput based on errors. Starts at the midpoint and scales up (0 errors) or down (>= threshold errors) each interval.
138
95
 
139
- for (let x = 0; x < 100; x++) {
96
+ ```ts
97
+ const throttle = createThrottledQueue({
98
+ min_rpi: 1,
99
+ max_rpi: 10,
100
+ interval: 1000,
101
+ errors_per_interval: 3,
102
+ onRateChange: (rate) => console.log(`Rate: ${rate}/interval`),
103
+ });
140
104
 
141
- throttle(function() {
142
- return !(Date.now() % 2);
143
- });
105
+ for (let i = 0; i < 100; i++) {
106
+ throttle(async () => {
107
+ const res = await fetch("https://api.example.com/data");
108
+ if (!res.ok) return false; // signals an error
109
+ });
144
110
  }
145
111
  ```
146
112
 
147
113
  ### Backoff
148
- By suppling `backoff:true` in the options, every time we hit the `errors_per_second` mark, we will backoff from the next batch of calls for 1 inteveral
149
114
 
150
- ```js
151
- const throttledQueue = require('dynamic-throttled-queue');
152
- const throttle = throttledQueue({min_rpi: 10, interval: 1000, backoff:true, errors_per_second:2});
153
- // at most make 10 requests every second, if more than 2 errors per second, then back off for 1 full interval of 1 second.
115
+ ```ts
116
+ const throttle = createThrottledQueue({
117
+ min_rpi: 10,
118
+ interval: 1000,
119
+ back_off: true,
120
+ errors_per_interval: 2,
121
+ });
122
+ ```
154
123
 
155
- for (let x = 0; x < 100; x++) {
124
+ ### Retry
156
125
 
157
- throttle(function() {
158
- return !(Date.now() % 2);
159
- });
160
- }
126
+ ```ts
127
+ const throttle = createThrottledQueue({
128
+ min_rpi: 5,
129
+ interval: 1000,
130
+ retry: 3, // retry failed callbacks up to 3 times
131
+ });
132
+
133
+ throttle(async () => {
134
+ const res = await fetch("https://api.example.com/data");
135
+ if (!res.ok) return false; // will be retried
136
+ });
161
137
  ```
162
138
 
139
+ ## Migration from v1
163
140
 
164
- ## Tests
165
- Note: The tests take a few minutes to run. Watch the console to see how closely the actual rate limit gets to the maximum.
166
- ### Node.js
167
- Run `npm test`.
168
- ### Browser
169
- Open `test/index.html` in your browser.
141
+ - `errors_per_second` removed — use `errors_per_interval` (counts errors per full interval window, not per second).
142
+ - `debug` option removed use `onRateChange` callback for observability.
170
143
 
144
+ ## Development
145
+
146
+ ```bash
147
+ pnpm install
148
+ pnpm build # build with zshy (ESM only)
149
+ pnpm test # run vitest
150
+ pnpm lint # type-check
151
+ ```
171
152
 
153
+ ## License
172
154
 
155
+ MIT
@@ -0,0 +1,21 @@
1
+ /** Return `false` to signal failure (increments error count, triggers retry if configured). */
2
+ export type ThrottleCallback = () => boolean | void | Promise<boolean | void>;
3
+ export type ThrottleOptions = {
4
+ min_rpi: number;
5
+ interval: number;
6
+ max_rpi?: number;
7
+ evenly_spaced?: boolean;
8
+ /** Max errors per interval before rate decrease. Default 5. */
9
+ errors_per_interval?: number;
10
+ back_off?: boolean;
11
+ retry?: number;
12
+ /** Minimum dead slots before queue compaction triggers. Default 512. */
13
+ compact_threshold?: number;
14
+ onRateChange?: (rate: number) => void;
15
+ };
16
+ export type ThrottleFn = (callback: ThrottleCallback) => void;
17
+ export type ThrottleHandle = ThrottleFn & {
18
+ stop: () => void;
19
+ readonly pending: number;
20
+ };
21
+ export declare function createThrottledQueue(options: ThrottleOptions): ThrottleHandle;
@@ -0,0 +1,142 @@
1
+ export function createThrottledQueue(options) {
2
+ const { min_rpi, interval, max_rpi = min_rpi, evenly_spaced = true, back_off = false, retry = 0, compact_threshold = 512, onRateChange, } = options;
3
+ const errors_per_interval = options.errors_per_interval ?? 5;
4
+ if (!Number.isInteger(min_rpi) || min_rpi < 1) {
5
+ throw new Error("min_rpi must be a positive integer");
6
+ }
7
+ if (!Number.isInteger(max_rpi) || max_rpi < min_rpi) {
8
+ throw new Error("max_rpi must be an integer >= min_rpi");
9
+ }
10
+ if (typeof interval !== "number" || interval <= 0) {
11
+ throw new Error("interval must be a positive number");
12
+ }
13
+ let current_rpi = Math.ceil((max_rpi + min_rpi) / 2);
14
+ let dyn_interval = evenly_spaced ? interval / current_rpi : interval;
15
+ let dyn_requests_per_interval = evenly_spaced ? 1 : current_rpi;
16
+ let error_count = 0;
17
+ let skippedLast = false;
18
+ let isRunning = false;
19
+ let last_called = 0;
20
+ let timeout;
21
+ let dynTimeout;
22
+ const queue = [];
23
+ let head = 0;
24
+ /** Halts timers. Retains unprocessed queue items; next enqueue resumes from where it left off. */
25
+ function stop() {
26
+ isRunning = false;
27
+ skippedLast = false;
28
+ clearTimeout(timeout);
29
+ timeout = undefined;
30
+ clearTimeout(dynTimeout);
31
+ dynTimeout = undefined;
32
+ if (head >= queue.length) {
33
+ queue.length = 0;
34
+ head = 0;
35
+ }
36
+ }
37
+ function handleResult(item, result) {
38
+ if (result === false) {
39
+ error_count++;
40
+ if (item.retries > 0) {
41
+ queue.push({ fn: item.fn, retries: item.retries - 1 });
42
+ if (!isRunning && queue.length > head) {
43
+ start();
44
+ }
45
+ }
46
+ }
47
+ }
48
+ function dequeue() {
49
+ const threshold = last_called + dyn_interval;
50
+ const now = Date.now();
51
+ if (now < threshold) {
52
+ clearTimeout(timeout);
53
+ timeout = setTimeout(dequeue, threshold - now);
54
+ return;
55
+ }
56
+ const end = Math.min(head + dyn_requests_per_interval, queue.length);
57
+ for (let i = head; i < end; i++) {
58
+ const item = queue[i];
59
+ let result;
60
+ try {
61
+ result = item.fn();
62
+ }
63
+ catch {
64
+ handleResult(item, false);
65
+ continue;
66
+ }
67
+ if (result instanceof Promise) {
68
+ void result.then(v => handleResult(item, v), () => handleResult(item, false));
69
+ }
70
+ else {
71
+ handleResult(item, result);
72
+ }
73
+ }
74
+ head = end;
75
+ last_called = Date.now();
76
+ // ponytail: splice is O(n), amortized by only firing when head > half array length
77
+ if (head > compact_threshold && head > queue.length / 2) {
78
+ queue.splice(0, head);
79
+ head = 0;
80
+ }
81
+ if (head >= queue.length) {
82
+ stop();
83
+ return;
84
+ }
85
+ timeout = setTimeout(dequeue, dyn_interval);
86
+ }
87
+ function applyRate(newRpi) {
88
+ if (newRpi === current_rpi)
89
+ return;
90
+ current_rpi = newRpi;
91
+ onRateChange?.(current_rpi);
92
+ if (evenly_spaced) {
93
+ dyn_interval = interval / current_rpi;
94
+ }
95
+ else {
96
+ dyn_requests_per_interval = current_rpi;
97
+ }
98
+ }
99
+ // ponytail: async callbacks resolve after adjustRate fires — error_count may lag by one interval under async load
100
+ function adjustRate() {
101
+ dynTimeout = undefined;
102
+ const wasSkipped = skippedLast;
103
+ skippedLast = false;
104
+ if (error_count >= errors_per_interval) {
105
+ applyRate(Math.max(min_rpi, current_rpi - 1));
106
+ if (isRunning && back_off && !wasSkipped) {
107
+ clearTimeout(timeout);
108
+ skippedLast = true;
109
+ timeout = setTimeout(dequeue, dyn_interval + interval);
110
+ }
111
+ }
112
+ else if (!wasSkipped && error_count === 0 && queue.length > head) {
113
+ applyRate(Math.min(max_rpi, current_rpi + 1));
114
+ }
115
+ error_count = 0;
116
+ if (isRunning) {
117
+ dynTimeout = setTimeout(adjustRate, interval);
118
+ }
119
+ }
120
+ function start() {
121
+ if (skippedLast)
122
+ return;
123
+ isRunning = true;
124
+ last_called = Date.now();
125
+ clearTimeout(timeout);
126
+ timeout = setTimeout(dequeue, dyn_interval);
127
+ if (!dynTimeout) {
128
+ dynTimeout = setTimeout(adjustRate, interval);
129
+ }
130
+ }
131
+ function enqueue(callback) {
132
+ queue.push({ fn: callback, retries: retry });
133
+ if (!isRunning) {
134
+ start();
135
+ }
136
+ }
137
+ enqueue.stop = stop;
138
+ // ponytail: defineProperty needed for getter; stop is plain assignment
139
+ Object.defineProperty(enqueue, "pending", { get: () => queue.length - head });
140
+ return enqueue;
141
+ }
142
+ //# sourceMappingURL=dynamic-throttled-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-throttled-queue.js","sourceRoot":"","sources":["../src/dynamic-throttled-queue.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,31 @@
1
1
  {
2
2
  "name": "dynamic-throttled-queue",
3
- "version": "1.1.3",
4
- "description": "Forked from shaunpersad/throttled-queue. Dynamically throttles arbitrary code to execute a minimum/maximum number of times per interval. Best for making throttled API requests.",
5
- "main": "dynamic-throttled-queue.js",
6
- "directories": {
7
- "test": "test"
3
+ "version": "2.0.0",
4
+ "type": "module",
5
+ "description": "Dynamically throttles arbitrary code to execute between a minimum and maximum number of times per interval. Best for making throttled API requests.",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "zshy": {
10
+ "exports": {
11
+ ".": "./src/dynamic-throttled-queue.ts"
12
+ },
13
+ "cjs": false
8
14
  },
9
15
  "scripts": {
10
- "test": "./node_modules/mocha/bin/mocha --recursive --timeout 200000"
16
+ "build": "zshy",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "lint:ts": "tsc --noEmit -p tsconfig.json",
20
+ "lint:esl": "eslint --config eslint.config.ts \"src/**/*.{j,t}s{,x}\" --cache --max-warnings=0",
21
+ "lint:esl:fix": "pnpm lint:esl --fix",
22
+ "lint:s": "eslint --config eslint.config.style.ts \"src/**/*.{j,t}s{,x}\" --cache --max-warnings=0",
23
+ "lint:fix": "pnpm lint:s --fix",
24
+ "lint:knip": "knip",
25
+ "lint:jscpd": "jscpd .",
26
+ "lint": "pnpm lint:ts && pnpm lint:esl",
27
+ "lint:e18e": "pnpm dlx @e18e/cli analyze",
28
+ "preinstall": "only-allow pnpm"
11
29
  },
12
30
  "repository": {
13
31
  "type": "git",
@@ -27,13 +45,30 @@
27
45
  "url": "https://github.com/adrianbrowning/dynamic-throttled-queue/issues"
28
46
  },
29
47
  "devDependencies": {
30
- "@babel/core": "^7.1.6",
31
- "@babel/preset-env": "^7.1.6",
32
- "gulp": "^4.0.0",
33
- "gulp-babel": "^8.0.0",
34
- "gulp-concat": "^2.6.1",
35
- "gulp-rename": "^1.4.0",
36
- "gulp-uglify": "^3.0.1",
37
- "mocha": "^5.2.0"
48
+ "@commitlint/cli": "20.0.0",
49
+ "@commitlint/config-conventional": "20.0.0",
50
+ "@gingacodemonkey/config": "^0.0.36",
51
+ "@types/node": "^24.13.3",
52
+ "eslint": "^9.39.5",
53
+ "husky": "9.0.6",
54
+ "jscpd": "^4.2.5",
55
+ "knip": "5.70.1",
56
+ "lint-staged": "15.2.10",
57
+ "only-allow": "^1.2.2",
58
+ "typescript": "^6.0.3",
59
+ "vitest": "^3.0.0",
60
+ "zshy": "^0.7.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=24.0.0",
64
+ "pnpm": ">=10.0.0"
65
+ },
66
+ "module": "./dist/dynamic-throttled-queue.js",
67
+ "types": "./dist/dynamic-throttled-queue.d.ts",
68
+ "exports": {
69
+ ".": {
70
+ "types": "./dist/dynamic-throttled-queue.d.ts",
71
+ "default": "./dist/dynamic-throttled-queue.js"
72
+ }
38
73
  }
39
74
  }
@@ -1,12 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="WEB_MODULE" version="4">
3
- <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$">
5
- <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
- <excludeFolder url="file://$MODULE_DIR$/temp" />
7
- <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
- </content>
9
- <orderEntry type="inheritedJdk" />
10
- <orderEntry type="sourceFolder" forTests="false" />
11
- </component>
12
- </module>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="Encoding">
4
- <file url="PROJECT" charset="UTF-8" />
5
- </component>
6
- </project>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="GitToolBoxBlameSettings">
4
- <option name="version" value="2" />
5
- </component>
6
- </project>
@@ -1,15 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="GitToolBoxProjectSettings">
4
- <option name="commitMessageIssueKeyValidationOverride">
5
- <BoolValueOverride>
6
- <option name="enabled" value="true" />
7
- </BoolValueOverride>
8
- </option>
9
- <option name="commitMessageValidationEnabledOverride">
10
- <BoolValueOverride>
11
- <option name="enabled" value="true" />
12
- </BoolValueOverride>
13
- </option>
14
- </component>
15
- </project>
@@ -1,7 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="CssBrowserCompatibilityForProperties" enabled="true" level="WARNING" enabled_by_default="true" />
5
- <inspection_tool class="t" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
6
- </profile>
7
- </component>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="EslintConfiguration">
4
- <option name="fix-on-save" value="true" />
5
- </component>
6
- </project>
package/.idea/misc.xml DELETED
@@ -1,79 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="JavaScriptSettings">
4
- <option name="languageLevel" value="ES6" />
5
- </component>
6
- <component name="ProjectInspectionProfilesVisibleTreeState">
7
- <entry key="Project Default">
8
- <profile-state>
9
- <expanded-state>
10
- <State>
11
- <id />
12
- </State>
13
- <State>
14
- <id>Code style issuesJavaScript</id>
15
- </State>
16
- <State>
17
- <id>CoffeeScript</id>
18
- </State>
19
- <State>
20
- <id>Control flow issuesJavaScript</id>
21
- </State>
22
- <State>
23
- <id>ECMAScript 6 migration aidsJavaScript</id>
24
- </State>
25
- <State>
26
- <id>General</id>
27
- </State>
28
- <State>
29
- <id>GeneralJavaScript</id>
30
- </State>
31
- <State>
32
- <id>JavaScript</id>
33
- </State>
34
- <State>
35
- <id>JavaScript validity issuesJavaScript</id>
36
- </State>
37
- <State>
38
- <id>Meteor</id>
39
- </State>
40
- <State>
41
- <id>Node.jsJavaScript</id>
42
- </State>
43
- <State>
44
- <id>Perl5</id>
45
- </State>
46
- <State>
47
- <id>Perl5 POD</id>
48
- </State>
49
- <State>
50
- <id>Potentially confusing code constructsJavaScript</id>
51
- </State>
52
- <State>
53
- <id>Probable bugsCoffeeScript</id>
54
- </State>
55
- <State>
56
- <id>Probable bugsJavaScript</id>
57
- </State>
58
- <State>
59
- <id>Template Toolkit 2</id>
60
- </State>
61
- <State>
62
- <id>Vue</id>
63
- </State>
64
- <State>
65
- <id>XPath</id>
66
- </State>
67
- <State>
68
- <id>XSLT</id>
69
- </State>
70
- </expanded-state>
71
- <selected-state>
72
- <State>
73
- <id>AngularJS</id>
74
- </State>
75
- </selected-state>
76
- </profile-state>
77
- </entry>
78
- </component>
79
- </project>
package/.idea/modules.xml DELETED
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/dynamic-throttled-queue.iml" filepath="$PROJECT_DIR$/.idea/dynamic-throttled-queue.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="PrettierConfiguration">
4
- <option name="myRunOnSave" value="true" />
5
- <option name="myRunOnReformat" value="true" />
6
- </component>
7
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- </component>
6
- </project>
@@ -1,242 +0,0 @@
1
- "use strict";
2
-
3
- (function () {
4
- // global on the server, window in the browser
5
- var previous_throttledQueue, debug = false;
6
-
7
- function debugFn(...msgs) {
8
- if (debug) console.log(msgs);
9
- }
10
-
11
- function typeOf(e) {
12
- return ({}).toString.call(e).match(/\s([a-zA-Z]+)/)[ 1 ].toLowerCase();
13
- }
14
-
15
- // Establish the root object, `window` (`self`) in the browser, `global`
16
- // on the server, or `this` in some virtual machines. We use `self`
17
- // instead of `window` for `WebWorker` support.
18
- const root = typeof self === 'object' && self.self === self && self ||
19
- typeof global === 'object' && global.global === global && global ||
20
- this;
21
-
22
- if (root != null) {
23
- previous_throttledQueue = root.throttledQueue;
24
- }
25
-
26
- /**
27
- * Factory function.
28
- *
29
- * @param min_rpi {number} - Minimum requests per interval
30
- * @param max_rpi [number=min_rpi] - Maximum requests per interval
31
- * @param interval {number} - Number of milliseconds between each batch of requests
32
- * @param evenly_spaced [boolean=true] - If true requests will be distributed throughout the interval time
33
- * @param errors_per_second [number=5] - Number of errors per second before deciding to either increase or decrease the current rpi
34
- * @param back_off [boolean=true] - If true and we hit the errors_per_interval watermark, we will back off for 1 interval
35
- * @param retry [number=0] - If greater than 0, any failed callbacks, will be put back onto the queue to retry upto X times
36
- * @param cb [Function] - Sends back the current rate
37
- *
38
- * @returns {Function}
39
- */
40
- const throttledQueue = function (options = {}) {
41
-
42
- const {
43
- min_rpi,
44
- interval,
45
- max_rpi = min_rpi,
46
- evenly_spaced = true,
47
- errors_per_second = 5,
48
- back_off = false,
49
- retry = 0,
50
- cb = (rate) => undefined
51
- } = options;
52
-
53
- debug = typeOf(options.debug) !== "undefined" ? options.debug === true : false;
54
-
55
- if (typeOf(min_rpi) !== "number") {
56
- throw new Error("min_rpi must be a number");
57
- }
58
- if (!Number.isInteger(min_rpi)) {
59
- throw new Error("min_rpi is not an integer");
60
- }
61
- if (min_rpi < 1) {
62
- throw new Error("min_rpi should be greater than 0");
63
- }
64
- if (typeOf(max_rpi) !== "number") {
65
- throw new Error("max_rpi must be a number");
66
- }
67
- if (!Number.isInteger(max_rpi)) {
68
- throw new Error("max_rpi is not an integer");
69
- }
70
- if (max_rpi < min_rpi) {
71
- throw new Error("max_rpi is less than min_rpi");
72
- }
73
- if (typeOf(interval) !== "number") {
74
- throw new Error("interval must be a number");
75
- }
76
- if (typeOf(errors_per_second) !== "number") {
77
- throw new Error("errors_per_second must be a number");
78
- }
79
- if (typeOf(evenly_spaced) !== "boolean") {
80
- throw new Error("evenly_spaced must be a boolean");
81
- }
82
- if (typeOf(back_off) !== "boolean") {
83
- throw new Error("back_off must be a boolean");
84
- }
85
- if (typeOf(retry) !== "number") {
86
- throw new Error("retry must be a number");
87
- }
88
-
89
- var dyn_interval = interval,
90
- dyn_requests_per_interval = Math.ceil(max_rpi - ((max_rpi - min_rpi) / 2)),
91
- current_rpi = dyn_requests_per_interval,
92
- error_count = 0,
93
- bSkippedLast = false,
94
- isRunning = false;
95
-
96
- /**
97
- * If all requests should be evenly spaced, adjust to suit.
98
- */
99
- if (evenly_spaced) {
100
- dyn_interval = dyn_interval / dyn_requests_per_interval;
101
- dyn_requests_per_interval = 1;
102
- }
103
-
104
- if (dyn_interval < 200) {
105
- console.warn('An interval of less than 200ms can create performance issues.');
106
- }
107
-
108
- const queue = [];
109
- let last_called = Date.now();
110
-
111
- /**
112
- * Gets called at a set interval to remove items from the queue.
113
- * This is a self-adjusting timer,
114
- * since the browser's setTimeout is highly inaccurate.
115
- */
116
- const dequeue = function () {
117
-
118
- const threshold = last_called + dyn_interval;
119
- const now = Date.now();
120
-
121
- /**
122
- * Adjust the timer if it was called too early.
123
- */
124
- if (now < threshold) {
125
- clearTimeout(timeout);
126
- timeout = setTimeout(dequeue, threshold - now);
127
- return;
128
- }
129
- const callbacks = queue.splice(0, dyn_requests_per_interval);
130
- for (let x = 0; x < callbacks.length; x++) {
131
- let cb = callbacks[ x ];
132
- let result = typeOf(cb) === "function" ? cb() : cb.fn();
133
- if (result === false) {
134
- error_count++;
135
- if (retry > 0) {
136
- if (typeOf(cb) === "function") {
137
- queue.push({retry, fn : cb});
138
- } else if (typeOf(cb) === "object" && (--cb.retry) !== 0) {
139
- queue.push(cb);
140
- }
141
- }
142
- }
143
- }
144
- bSkippedLast = false;
145
-
146
- last_called = Date.now();
147
- if (queue.length === 0) {
148
- isRunning = !1;
149
- clearTimeout(timeout);
150
- clearTimeout(dyn_timeout);
151
- return;
152
- }
153
- timeout = setTimeout(dequeue, dyn_interval);
154
- };
155
- const alterDYN_rpi = function () {
156
-
157
- debugFn('Error Count:', error_count);
158
- if (error_count >= errors_per_second) {
159
- debugFn('Decreasing rate limit');
160
- current_rpi = Math.max(min_rpi, current_rpi - 1);
161
- cb && cb(current_rpi);
162
- //decrease dyn count by 1
163
- if (evenly_spaced) {
164
- dyn_interval = interval / current_rpi;
165
- } else {
166
- dyn_requests_per_interval = current_rpi;
167
- }
168
-
169
- if (back_off && !bSkippedLast) {
170
- const threshold = last_called + dyn_interval;
171
- const now = Date.now();
172
-
173
- /**
174
- * Adjust the timer if it was called too early.
175
- */
176
- if (now < threshold) {
177
- clearTimeout(timeout);
178
- bSkippedLast = true;
179
- timeout = setTimeout(dequeue, (threshold - now) + interval);
180
- }
181
- }
182
-
183
- } else if (!bSkippedLast && error_count === 0 && queue.length > 0) {
184
- debugFn('Increasing rate limit');
185
- current_rpi = Math.min(max_rpi, current_rpi + 1);
186
- cb && cb(current_rpi);
187
- //increase dyn count by 1
188
- if (evenly_spaced) {
189
- dyn_interval = interval / current_rpi;
190
- } else {
191
- dyn_requests_per_interval = current_rpi;
192
- }
193
- }
194
- error_count = 0;
195
- cb && cb(current_rpi);
196
- debugFn(`current_rpi: ${current_rpi}`);
197
- dyn_timeout = setTimeout(alterDYN_rpi, interval);
198
- };
199
-
200
- /**
201
- * Kick off the timer.
202
- */
203
- var timeout = setTimeout(dequeue, dyn_interval);
204
-
205
- var dyn_timeout = setTimeout(alterDYN_rpi, interval);
206
-
207
- isRunning = !0;
208
-
209
- /**
210
- * Return a function that can enqueue items.
211
- */
212
- return function (callback) {
213
- queue.push(callback);
214
- if (!isRunning) {
215
- isRunning = !0;
216
- timeout = setTimeout(dequeue, dyn_interval);
217
- dyn_timeout = setTimeout(alterDYN_rpi, interval);
218
- }
219
- };
220
- };
221
-
222
- throttledQueue.noConflict = function () {
223
- root.throttledQueue = previous_throttledQueue;
224
- return throttledQueue;
225
- };
226
-
227
- // Node.js
228
- if (typeof module === 'object' && module.exports) {
229
- module.exports = throttledQueue;
230
- }
231
- // AMD / RequireJS
232
- else if (typeof define === 'function' && define.amd) {
233
- define([], function () {
234
- return throttledQueue;
235
- });
236
- }
237
- // included directly via <script> tag
238
- else {
239
- root.throttledQueue = throttledQueue;
240
- }
241
-
242
- }).call(this);
@@ -1 +0,0 @@
1
- "use strict";function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}(function(){var e,S=!1;function D(){for(var e=arguments.length,r=new Array(e),t=0;t<e;t++)r[t]=arguments[t];S&&console.log(r)}function j(e){return{}.toString.call(e).match(/\s([a-zA-Z]+)/)[1].toLowerCase()}var r="object"===("undefined"==typeof self?"undefined":_typeof(self))&&self.self===self&&self||"object"===("undefined"==typeof global?"undefined":_typeof(global))&&global.global===global&&global||this;null!=r&&(e=r.throttledQueue);var t=function(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},o=e.min_rpi,n=e.interval,r=e.max_rpi,i=void 0===r?o:r,t=e.evenly_spaced,u=void 0===t||t,a=e.errors_per_second,f=void 0===a?5:a,l=e.back_off,s=void 0!==l&&l,m=e.retry,c=void 0===m?0:m;if(S="undefined"!==j(e.debug)&&!0===e.debug,"number"!==j(o))throw new Error("min_rpi must be a number");if(!Number.isInteger(o))throw new Error("min_rpi is not an integer");if(o<1)throw new Error("min_rpi should be greater than 0");if("number"!==j(i))throw new Error("max_rpi must be a number");if(!Number.isInteger(i))throw new Error("max_rpi is not an integer");if(i<o)throw new Error("max_rpi is less than min_rpi");if("number"!==j(n))throw new Error("interval must be a number");if("number"!==j(f))throw new Error("errors_per_second must be a number");if("boolean"!==j(u))throw new Error("evenly_spaced must be a boolean");if("boolean"!==j(s))throw new Error("back_off must be a boolean");if("number"!==j(c))throw new Error("retry must be a number");var b=n,d=Math.ceil(i-(i-o)/2),p=d,h=0,w=!1,y=!1;u&&(b/=d,d=1),b<200&&console.warn("An interval of less than 200ms can create performance issues.");var v=[],_=Date.now(),g=function e(){var r=_+b,t=Date.now();if(t<r)return clearTimeout(T),void(T=setTimeout(e,r-t));for(var o=v.splice(0,d),n=0;n<o.length;n++){var i=o[n];!1===("function"===j(i)?i():i.fn())&&(h++,0<c&&("function"===j(i)?v.push({retry:c,fn:i}):"object"===j(i)&&0!=--i.retry&&v.push(i)))}if(w=!1,_=Date.now(),0===v.length)return y=!1,clearTimeout(T),void clearTimeout(x);T=setTimeout(e,b)},E=function e(){if(D("Error Count:",h),f<=h){if(D("Decreasing rate limit"),p=Math.max(o,p-1),u?b=n/p:d=p,s&&!w){var r=_+b,t=Date.now();t<r&&(clearTimeout(T),w=!0,T=setTimeout(g,r-t+n))}}else!w&&0===h&&0<v.length&&(D("Increasing rate limit"),p=Math.min(i,p+1),u?b=n/p:d=p);h=0,D("current_rpi: ".concat(p)),x=setTimeout(e,n)},T=setTimeout(g,b),x=setTimeout(E,n);return y=!0,function(e){v.push(e),y||(y=!0,T=setTimeout(g,b),x=setTimeout(E,n))}};t.noConflict=function(){return r.throttledQueue=e,t},"object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports?module.exports=t:"function"==typeof define&&define.amd?define([],function(){return t}):r.throttledQueue=t}).call(void 0);
package/test/index.html DELETED
@@ -1,27 +0,0 @@
1
- <html>
2
- <head>
3
- <meta charset="utf-8">
4
- <title>Mocha Tests</title>
5
- <link href="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css" rel="stylesheet" />
6
- </head>
7
- <body>
8
- <div id="mocha"></div>
9
-
10
- <script src="https://cdn.rawgit.com/jquery/jquery/2.1.4/dist/jquery.min.js"></script>
11
- <script src="https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js"></script>
12
- <script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script>
13
-
14
- <script>
15
- mocha.setup('bdd');
16
- mocha.setup({
17
- timeout: 200000
18
- });
19
- mocha.checkLeaks();
20
- </script>
21
- <script src="../dynamic-throttled-queue.min.js"></script>
22
- <script src="tests.js"></script>
23
- <script>
24
- mocha.run();
25
- </script>
26
- </body>
27
- </html>
package/test/node.js DELETED
@@ -1,2 +0,0 @@
1
- global.throttledQueue = require('../dynamic-throttled-queue');
2
-
package/test/tests.js DELETED
@@ -1,203 +0,0 @@
1
- function calculateRPMS(num_requests, time_started) {
2
-
3
- return num_requests / (Date.now() - time_started);
4
-
5
- }
6
-
7
- function cb(rate) {
8
- console.log('Rate:', rate);
9
- }
10
-
11
- describe('dynamic-throttled-queue', function() {
12
-
13
- it('should queue all callbacks', function(done) {
14
-
15
- var requests_per_interval = 1;
16
- var interval = 200;
17
- var throttle = throttledQueue({min_rpi:requests_per_interval, interval:interval, cb});
18
- var num_requests = 0;
19
- var request_limit = 100;
20
- for (var x = 0; x < request_limit; x++) {
21
- throttle(function() {
22
- console.log('Throttling...');
23
- num_requests++;
24
- });
25
- }
26
- throttle(function() {
27
- if (num_requests !== request_limit) {
28
- throw new Error('Not all callbacks queued.');
29
- }
30
- done();
31
- });
32
- });
33
-
34
- it('should queue the callback within the interval', function(done) {
35
-
36
- var requests_per_interval = 1;
37
- var interval = 200;
38
- var throttle = throttledQueue({min_rpi:requests_per_interval, interval:interval, cb});
39
- var last_executed = Date.now();
40
-
41
- var num_requests = 0;
42
- var request_limit = 100;
43
-
44
- for (var x = 0; x < request_limit; x++) {
45
- throttle(function() {
46
- console.log('Throttling...');
47
- var now = Date.now();
48
- var time_elapsed = now - last_executed;
49
- if (time_elapsed < interval) {
50
- throw new Error('Did not honor interval.');
51
- }
52
- last_executed = now;
53
- num_requests++;
54
- });
55
- }
56
- throttle(function() {
57
- if (num_requests !== request_limit) {
58
- throw new Error('Not all callbacks queued.');
59
- }
60
- done();
61
- });
62
- });
63
-
64
- it('should queue the callback and honor the interval', function(done) {
65
-
66
- var requests_per_interval = 1;
67
- var interval = 500;
68
- var throttle = throttledQueue({min_rpi:requests_per_interval, interval:interval, cb});
69
- var time_started = Date.now();
70
- var max_rpms = requests_per_interval / interval;
71
-
72
- var num_requests = 0;
73
- var request_limit = 100;
74
-
75
- for (var x = 0; x < request_limit; x++) {
76
- throttle(function() {
77
- var rpms = calculateRPMS(++num_requests, time_started);
78
- console.log(rpms, max_rpms);
79
- if (rpms > max_rpms) {
80
- throw new Error('Did not honor interval.');
81
- }
82
- });
83
- }
84
- throttle(function() {
85
- if (num_requests !== request_limit) {
86
- throw new Error('Not all callbacks queued.');
87
- }
88
- done();
89
- });
90
- });
91
-
92
- it('should queue the callback and honor the interval with multiple requests per interval', function(done) {
93
-
94
- var requests_per_interval = 3;
95
- var interval = 1000;
96
- var throttle = throttledQueue({min_rpi:requests_per_interval, interval:interval, cb});
97
- var time_started = Date.now();
98
- var max_rpms = requests_per_interval / interval;
99
-
100
- var num_requests = 0;
101
- var request_limit = 100;
102
-
103
- for (var x = 0; x < request_limit; x++) {
104
- throttle(function() {
105
- var rpms = calculateRPMS(++num_requests, time_started);
106
- console.log(rpms, max_rpms);
107
- if (rpms > max_rpms) {
108
- throw new Error('Did not honor interval.');
109
- }
110
- });
111
- }
112
- throttle(function() {
113
- if (num_requests !== request_limit) {
114
- throw new Error('Not all callbacks queued.');
115
- }
116
- done();
117
- });
118
- });
119
-
120
- it('should queue the callback and honor the interval with multiple evenly spaced requests per interval', function(done) {
121
-
122
- var requests_per_interval = 3;
123
- var interval = 1000;
124
- var throttle = throttledQueue({min_rpi:requests_per_interval, interval:interval, cb}, true);
125
- var time_started = Date.now();
126
- var max_rpms = requests_per_interval / interval;
127
-
128
- var num_requests = 0;
129
- var request_limit = 100;
130
-
131
- for (var x = 0; x < request_limit; x++) {
132
- throttle(function() {
133
- var rpms = calculateRPMS(++num_requests, time_started);
134
- console.log(rpms, max_rpms);
135
- if (rpms > max_rpms) {
136
- throw new Error('Did not honor interval.');
137
- }
138
- });
139
- }
140
- throttle(function() {
141
- if (num_requests !== request_limit) {
142
- throw new Error('Not all callbacks queued.');
143
- }
144
- done();
145
- });
146
- });
147
-
148
- it('should add items back to the queue if return false', function(done){
149
- const requests_per_interval = 5;
150
- const interval = 200;
151
- const retry = 2;
152
- const throttle = throttledQueue({min_rpi:requests_per_interval, interval:interval,evenly_spaced:false, retry, cb}, true);
153
-
154
- let num_requests = 0;
155
- const request_limit = 5;
156
- const max_requests = request_limit * (retry + 1);
157
-
158
- const callBacks = {};
159
-
160
- for (let x = 0; x < request_limit; x++) {
161
- throttle(function() {
162
- callBacks[x] = callBacks[x] || 0;
163
- callBacks[x]++;
164
- num_requests++;
165
-
166
-
167
- if (num_requests === max_requests ) {
168
- done();
169
- }
170
-
171
- return false;
172
- });
173
- }
174
- });
175
-
176
- it('should add half items back to the queue if return false mod 2', function(done){
177
- const requests_per_interval = 5;
178
- const interval = 200;
179
- const retry = 2;
180
- const throttle = throttledQueue({min_rpi:requests_per_interval, interval:interval,evenly_spaced:false, retry, cb}, true);
181
-
182
- let num_requests = 0;
183
- const request_limit = 5;
184
- const max_requests = (request_limit * retry) + 1;
185
-
186
- const callBacks = {};
187
-
188
- for (let x = 0; x < request_limit; x++) {
189
- throttle(function() {
190
- callBacks[x] = callBacks[x] || 0;
191
- callBacks[x]++;
192
- num_requests++;
193
-
194
-
195
- if (num_requests === max_requests ) {
196
- done();
197
- }
198
-
199
- return !!(x % 2);
200
- });
201
- }
202
- });
203
- });