add jsr
This commit is contained in:
parent
a6c579e429
commit
6884386903
15
README.md
15
README.md
|
@ -24,21 +24,20 @@ bun add github:nesterow/limiter # or pnpm
|
|||
### Limit number of requests
|
||||
|
||||
```typescript
|
||||
import {Limiter} from '@nesterow/limiter'
|
||||
import { Limiter } from "@nesterow/limiter";
|
||||
|
||||
const task = () => {
|
||||
await fetch('https://my.api.xyz')
|
||||
await fetch("https://my.api.xyz");
|
||||
// ... write
|
||||
}
|
||||
};
|
||||
|
||||
const limiter = new Limiter({
|
||||
limit: 10
|
||||
})
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
for (let i=0; i<100; i++) {
|
||||
await limiter.process(task)
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await limiter.process(task);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Limit RPS
|
||||
|
|
30
dist/limiter.js
vendored
30
dist/limiter.js
vendored
|
@ -16,7 +16,7 @@ export class Limiter {
|
|||
#maxRetry = 0;
|
||||
#rps;
|
||||
#onError;
|
||||
constructor({ limit = 10, rps, maxRetry = 0, onError = undefined, }) {
|
||||
constructor({ limit = 10, rps, maxRetry = 0, onError = undefined }) {
|
||||
this.#limit = limit;
|
||||
this.#rps = rps;
|
||||
this.#maxRetry = maxRetry;
|
||||
|
@ -41,15 +41,13 @@ export class Limiter {
|
|||
try {
|
||||
await Promise.all(this.#promises);
|
||||
this.#promises = [];
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
if (!this.#onError) {
|
||||
throw error;
|
||||
}
|
||||
for (;;) {
|
||||
const promise = this.#promises.pop();
|
||||
if (!promise)
|
||||
break;
|
||||
if (!promise) break;
|
||||
promise.catch(this.#onError);
|
||||
}
|
||||
}
|
||||
|
@ -57,8 +55,7 @@ export class Limiter {
|
|||
async process(...callbacks) {
|
||||
for (;;) {
|
||||
const item = callbacks.pop();
|
||||
if (!item)
|
||||
break;
|
||||
if (!item) break;
|
||||
if (this.#promisesCount >= this.#limit) {
|
||||
await this.#execute();
|
||||
}
|
||||
|
@ -69,8 +66,7 @@ export class Limiter {
|
|||
const res = await this.#limitRps(callback);
|
||||
this.#promisesCount--;
|
||||
return res;
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
this.#promisesCount--;
|
||||
if (this.#maxRetry > 0) {
|
||||
this.#retryQueue.push({
|
||||
|
@ -78,8 +74,7 @@ export class Limiter {
|
|||
retries: item.retries ?? this.#maxRetry,
|
||||
error: error,
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
@ -93,16 +88,15 @@ export class Limiter {
|
|||
const retryItems = [];
|
||||
for (;;) {
|
||||
const item = this.#retryQueue.pop();
|
||||
if (!item)
|
||||
break;
|
||||
if (!item) break;
|
||||
if (item.retries > 0) {
|
||||
item.retries--;
|
||||
retryItems.push(item);
|
||||
}
|
||||
else if (this.#onError) {
|
||||
this.#onError(new LimiterRetryError("Retry limit exceeded", item.error));
|
||||
}
|
||||
else {
|
||||
} else if (this.#onError) {
|
||||
this.#onError(
|
||||
new LimiterRetryError("Retry limit exceeded", item.error),
|
||||
);
|
||||
} else {
|
||||
throw new LimiterRetryError("Retry limit exceeded", item.error);
|
||||
}
|
||||
}
|
||||
|
|
50
dist/limiter.test.js
vendored
50
dist/limiter.test.js
vendored
|
@ -7,8 +7,7 @@ const setup = ({ send, close, delay = 300 }) => {
|
|||
let loading = false;
|
||||
return {
|
||||
process: jest.fn(async () => {
|
||||
if (closed)
|
||||
throw new Error("Connection closed");
|
||||
if (closed) throw new Error("Connection closed");
|
||||
//if (loading) throw new Error("Connection in use");
|
||||
loading = true;
|
||||
await send();
|
||||
|
@ -31,9 +30,11 @@ test("Limiter: opens #limit of concurent connections", async () => {
|
|||
});
|
||||
const limiter = new Limiter({ limit: 3 });
|
||||
const connections = Array.from({ length: 7 }, () => connection());
|
||||
limiter.process(...connections.map((c) => {
|
||||
limiter.process(
|
||||
...connections.map((c) => {
|
||||
return c.process;
|
||||
}));
|
||||
}),
|
||||
);
|
||||
await delay(0);
|
||||
expect(limiter.length).toBe(3);
|
||||
await delay(500);
|
||||
|
@ -72,15 +73,18 @@ test("Limiter: limit RPS - requests are evenly distributed", async () => {
|
|||
const connections = Array.from({ length: 45 }, () => connection());
|
||||
let count = 0;
|
||||
const timestamps = [];
|
||||
await limiter.process(...connections.map((c) => {
|
||||
await limiter.process(
|
||||
...connections.map((c) => {
|
||||
return () => {
|
||||
++count;
|
||||
timestamps.push(Date.now());
|
||||
return c.process();
|
||||
};
|
||||
}));
|
||||
}),
|
||||
);
|
||||
expect(count).toBe(45);
|
||||
const diffsAvg = timestamps
|
||||
const diffsAvg =
|
||||
timestamps
|
||||
.map((t, i) => {
|
||||
return i === 0 ? 100 : t - timestamps[i - 1];
|
||||
})
|
||||
|
@ -97,11 +101,12 @@ test("Limiter: throws an error by deafult", async () => {
|
|||
const limiter = new Limiter({ limit: 3 });
|
||||
const connections = Array.from({ length: 6 }, () => connection());
|
||||
try {
|
||||
await limiter.process(...connections.map((c) => {
|
||||
await limiter.process(
|
||||
...connections.map((c) => {
|
||||
return c.process;
|
||||
}));
|
||||
}
|
||||
catch (e) {
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e).toBe(1);
|
||||
}
|
||||
expect(limiter.length).toBe(0);
|
||||
|
@ -113,15 +118,17 @@ test("Limiter: #onError, no trow", async () => {
|
|||
close: jest.fn(() => Promise.resolve()),
|
||||
delay: 500,
|
||||
});
|
||||
const onError = jest.fn(() => { });
|
||||
const onError = jest.fn(() => {});
|
||||
const limiter = new Limiter({
|
||||
limit: 3,
|
||||
onError,
|
||||
});
|
||||
const connections = Array.from({ length: 6 }, () => connection());
|
||||
await limiter.process(...connections.map((c) => {
|
||||
await limiter.process(
|
||||
...connections.map((c) => {
|
||||
return c.process;
|
||||
}));
|
||||
}),
|
||||
);
|
||||
expect(limiter.length).toBe(0);
|
||||
expect(connections[0].send).toBeCalledTimes(6);
|
||||
expect(onError).toBeCalledTimes(6);
|
||||
|
@ -139,12 +146,13 @@ test("Limiter: #maxRetry, exit on fail", async () => {
|
|||
const connections = Array.from({ length: 6 }, () => connection());
|
||||
let count = 0;
|
||||
try {
|
||||
await limiter.process(...connections.map((c) => {
|
||||
await limiter.process(
|
||||
...connections.map((c) => {
|
||||
++count;
|
||||
return c.process;
|
||||
}));
|
||||
}
|
||||
catch (e) {
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(LimiterRetryError);
|
||||
}
|
||||
expect(limiter.length).toBe(0);
|
||||
|
@ -165,9 +173,11 @@ test("Limiter: #onError, #maxRetry", async () => {
|
|||
onError,
|
||||
});
|
||||
const connections = Array.from({ length: 6 }, () => connection());
|
||||
await limiter.process(...connections.map((c) => {
|
||||
await limiter.process(
|
||||
...connections.map((c) => {
|
||||
return c.process;
|
||||
}));
|
||||
}),
|
||||
);
|
||||
expect(onError).toBeCalledTimes(6);
|
||||
expect(error).toBeInstanceOf(LimiterRetryError);
|
||||
});
|
||||
|
|
5
jsr.json
Normal file
5
jsr.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@nesterow/limiter",
|
||||
"version": "0.1.0",
|
||||
"exports": "./limiter.ts"
|
||||
}
|
|
@ -140,7 +140,7 @@ export class Limiter implements ILimiter {
|
|||
}
|
||||
}
|
||||
|
||||
get length() {
|
||||
get length(): number {
|
||||
return this.#promisesCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"name": "@nesterow/limiter",
|
||||
"module": "limiter.ts",
|
||||
"type": "module",
|
||||
|
|
Loading…
Reference in a new issue