diff --git a/package-lock.json b/package-lock.json index 4fcf8ccdf4..74af390cff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,6 @@ "ajv": "^8.12.0", "aws4": "^1.11.0", "axios": "^1.6.0", - "axios-concurrency": "^1.0.4", "axios-retry": "^3.2.0", "clipboard-copy": "^3.2.0", "d3": "^7.8.5", @@ -12205,22 +12204,6 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/axios-concurrency": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "axios": "^0.21.1" - } - }, - "node_modules/axios-concurrency/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, "node_modules/axios-retry": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.6.0.tgz", diff --git a/package.json b/package.json index 4e998e199a..9e998a6040 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,6 @@ "ajv": "^8.12.0", "aws4": "^1.11.0", "axios": "^1.6.0", - "axios-concurrency": "^1.0.4", "axios-retry": "^3.2.0", "clipboard-copy": "^3.2.0", "d3": "^7.8.5", diff --git a/src/ui/libs/DatalensChartkit/modules/axios/axios.ts b/src/ui/libs/DatalensChartkit/modules/axios/axios.ts index f3e56c589a..2ae7ab7a89 100644 --- a/src/ui/libs/DatalensChartkit/modules/axios/axios.ts +++ b/src/ui/libs/DatalensChartkit/modules/axios/axios.ts @@ -1,11 +1,11 @@ import axios, {AxiosError} from 'axios'; -// @ts-ignore -import {ConcurrencyManager as concurrencyManager} from 'axios-concurrency'; import axiosRetry, {isRetryableError} from 'axios-retry'; import isNumber from 'lodash/isNumber'; import {showReadOnlyToast} from 'ui/utils/readOnly'; -let concurrencyManagerInstance: typeof concurrencyManager = null; +import {ConcurrencyManagerInstance, concurrencyManager} from './axiosConcurrency'; + +let concurrencyManagerInstance: ConcurrencyManagerInstance; const client = axios.create({ withCredentials: true, @@ -17,7 +17,7 @@ const client = axios.create({ initConcurrencyManager(Infinity); export function initConcurrencyManager(maxConcurrentRequests: number) { - if (concurrencyManagerInstance) { + if (concurrencyManagerInstance !== undefined) { concurrencyManagerInstance.detach(); } diff --git a/src/ui/libs/DatalensChartkit/modules/axios/axiosConcurrency.ts b/src/ui/libs/DatalensChartkit/modules/axios/axiosConcurrency.ts new file mode 100644 index 0000000000..f06c4fb1d4 --- /dev/null +++ b/src/ui/libs/DatalensChartkit/modules/axios/axiosConcurrency.ts @@ -0,0 +1,98 @@ +// based on https://github.com/bernawil/axios-concurrency/ + +import {AxiosInstance, AxiosResponse, InternalAxiosRequestConfig} from 'axios'; + +type Handler = { + request: InternalAxiosRequestConfig; + resolver(value: unknown): void; +}; + +type Interceptors = { + request: number; + response: number; +}; + +export type ConcurrencyManagerInstance = { + queue: Handler[]; + running: Handler[]; + shiftInitial: () => void; + push: (reqHandler: Handler) => void; + shift: () => void; + requestHandler(req: InternalAxiosRequestConfig): Promise; + responseHandler(res: AxiosResponse): AxiosResponse; + responseErrorHandler(res: AxiosResponse): void; + interceptors: Interceptors; + detach(): void; +}; + +export const concurrencyManager = ( + axios: AxiosInstance, + MAX_CONCURRENT = 10, +): ConcurrencyManagerInstance => { + if (MAX_CONCURRENT < 1) { + throw 'Concurrency Manager Error: minimun concurrent requests is 1'; + } + const instance: ConcurrencyManagerInstance = { + queue: [], + running: [], + shiftInitial: () => { + setTimeout(() => { + if (instance.running.length < MAX_CONCURRENT) { + instance.shift(); + } + }, 0); + }, + push: (reqHandler) => { + instance.queue.push(reqHandler); + instance.shiftInitial(); + }, + shift: () => { + if (instance.queue.length) { + const queued = instance.queue.shift(); + if (queued) { + if (queued.request.cancelToken && queued.request.cancelToken.reason) { + // the request was already cancelled - do not even start it, just forget it + instance.shift(); + return; + } + queued.resolver(queued.request); + instance.running.push(queued); + } + } + }, + // Use as interceptor. Queue outgoing requests + requestHandler: (req) => { + return new Promise((resolve) => { + instance.push({request: req, resolver: resolve}); + }); + }, + // Use as interceptor. Execute queued request upon receiving a response + responseHandler: (res) => { + instance.running.shift(); + instance.shift(); + return res; + }, + responseErrorHandler: (res) => { + return Promise.reject(instance.responseHandler(res)); + }, + interceptors: { + request: -1, + response: -1, + }, + detach: () => { + if (instance.interceptors.request !== -1) { + axios.interceptors.request.eject(instance.interceptors.request); + } + if (instance.interceptors.response !== -1) { + axios.interceptors.response.eject(instance.interceptors.response); + } + }, + }; + // queue concurrent requests + instance.interceptors.request = axios.interceptors.request.use(instance.requestHandler); + instance.interceptors.response = axios.interceptors.response.use( + instance.responseHandler, + instance.responseErrorHandler, + ); + return instance; +};