Skip to content

Commit

Permalink
amends interceptors to handle promise concerency correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
lcflight committed Feb 28, 2024
1 parent 95da955 commit 2a2f30e
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 28 deletions.
10 changes: 8 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/no-unused-vars": ["error", { "args": "all" }]
"@typescript-eslint/no-unused-vars": [
"error",
{ "args": "all", "argsIgnorePattern": "^_" }
]
},
"overrides": [
{
Expand All @@ -26,7 +29,10 @@
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/unbound-method": "off",
"@typescript-eslint/no-unsafe-assignment": "off"
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-member-access": "off"
}
}
]
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist/
node_modules
node_modules
scripts/
33 changes: 8 additions & 25 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,17 @@
import axios, { InternalAxiosRequestConfig } from "axios";

const DEFAULT_PARAMS = {
user_field_names: true,
};

const MAX_REQUESTS_COUNT = 1;
const INTERVAL_MS = 10;
import axios from "axios";
import { Interceptors } from "./interceptors.js";

const client = axios.create();

client.defaults.baseURL = "https://baserow.taskratchet.com/api/";
client.defaults.headers.common["Content-Type"] = "application/json";

let pendingRequests = 0;

client.interceptors.request.use((config: InternalAxiosRequestConfig) => {
config.params = {
...DEFAULT_PARAMS,
...(config.params as Record<string, unknown>),
};
const interceptors = new Interceptors();

return new Promise((resolve) => {
const interval = setInterval(() => {
if (pendingRequests < MAX_REQUESTS_COUNT) {
pendingRequests++;
clearInterval(interval);
resolve(config);
}
}, INTERVAL_MS);
});
});
client.interceptors.request.use((c) => interceptors.onRequest(c));
client.interceptors.response.use(
(r) => interceptors.onResponse(r),
(e) => interceptors.onError(e),
);

export default client;
40 changes: 40 additions & 0 deletions src/interceptors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, it, expect, beforeEach } from "vitest";
import { MAX_REQUESTS_COUNT, INTERVAL_MS, Interceptors } from "./interceptors";

describe("Request Interceptor", () => {
let interceptors: Interceptors;

beforeEach(() => {
interceptors = new Interceptors();
});

it("limits concurrency", async () => {
const promises = Array.from({ length: MAX_REQUESTS_COUNT + 1 }, () =>
interceptors.onRequest({} as any),
);

await expect(() => {
const all = Promise.all(promises);
return Promise.race([
all,
new Promise((_, reject) =>
setTimeout(() => reject(new Error()), INTERVAL_MS * 2),
),
]);
}).rejects.toThrow();
});

it("resolves all promises", async () => {
const promises = Array.from({ length: MAX_REQUESTS_COUNT + 1 }, () =>
interceptors.onRequest({} as any),
);

await new Promise<void>((resolve) => setTimeout(resolve, INTERVAL_MS));

Array.from({ length: MAX_REQUESTS_COUNT + 1 }, () =>
interceptors.onResponse({} as any),
);

await Promise.all(promises);
});
});
41 changes: 41 additions & 0 deletions src/interceptors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { InternalAxiosRequestConfig } from "axios";

const DEFAULT_PARAMS = {
user_field_names: true,
};

export const MAX_REQUESTS_COUNT = 1;
export const INTERVAL_MS = 10;

export class Interceptors {
private pending = 0;

public onRequest(
config: InternalAxiosRequestConfig,
): Promise<InternalAxiosRequestConfig> {
config.params = {
...DEFAULT_PARAMS,
...(config.params as Record<string, unknown>),
};

return new Promise((resolve) => {
const interval = setInterval(() => {
if (this.pending < MAX_REQUESTS_COUNT) {
this.pending++;
clearInterval(interval);
resolve(config);
}
}, INTERVAL_MS);
});
}

public onResponse<T>(response: T): Promise<T> {
this.pending = Math.max(0, this.pending - 1);
return Promise.resolve(response);
}

public onError<T>(error: T): Promise<T> {
this.pending = Math.max(0, this.pending - 1);
return Promise.reject(error);
}
}

0 comments on commit 2a2f30e

Please sign in to comment.