Ограничить максимальное число параллельных запросов

Рейтинг: 1Ответов: 0Опубликовано: 07.01.2023

Имеется следующая функция:

export function getImage(requestParameters: MapLibreRequestParameters): MapLibreRequest<MapLibreResponse<ImageBitmap | HTMLImageElement>> {
    const request = helper.getArrayBuffer(requestParameters);

    return {
        response: (async () => {
            const response = await request.response;
            const image = await arrayBufferToCanvasImageSource(response.data);

            return {
                data: image,
                cacheControl: response.cacheControl,
                expires: response.expires
            };
        })(),

        cancel: request.cancel
    };
}

Она синхронная, но возвращает объект, состоящий из двух полей: response - Promise, который резолвится объектом (3 поля: data, cacheControl, expires, но это для нас неважно) и cancel - метод, который отменяет запрос.

Эта функция работает как нужно и все с ней хорошо. Однако, мне нужно реализовать дополнительное ограничение. Нужно сделать так, чтобы количество параллельных (одновременных) запросов к сети в любой момент времени не превышало n.

Таким образом, если n === 0, не должно быть сделано ни одного запроса. Если n === 1, то одновременно может загружаться только одно изображение (то есть все изображения загружаются последовательно). При n > 1 < m одновременно загружаться могут не более m изображений.


Мое решение

Исходя из того что функция getImage синхронная, то строка

const request = helper.getArrayBuffer(requestParameters);

выполняется сразу же при вызове getImage. Такое нам не нужно, нам нужно выполнение самого запроса отложить. Поэтому переменную request заменим на функцию requestMaker, которую будем вызывать только тогда, когда нам это нужно:

export function getImage(requestParameters: MapLibreRequestParameters): MapLibreRequest<MapLibreResponse<ImageBitmap | HTMLImageElement>> {
    if (webpSupported.supported) {
        if (!requestParameters.headers) requestParameters.headers = {};
        requestParameters.headers['Accept'] = 'image/webp,*/*';
    }

    function requestMaker() {
        const request = helper.getArrayBuffer(requestParameters);
        return request;
    }

    return {
        response: (async () => {
            const response = await requestMaker().response;
            const image = await arrayBufferToCanvasImageSource(response.data);

            return {
                data: image,
                cacheControl: response.cacheControl,
                expires: response.expires
            };
        })(),

        cancel() {
            //
        }
    };
}

(cancel пока опустим).

Теперь выполнение этой функции requestMaker, делающей сам запрос, нужно отложить до какого-то момента.

Предположим, что сейчас мы пытаемся решить задачу только для n === 1.

Создадим массив, в котором будем хранить все запросы, выполняющиеся в данных момент:

const ongoingImageRequests = [];

Теперь внутри requestMaker будем сохранять в эту переменную запросы как только они происходят, и удалять их как только мы получаем ответ:

const ongoingImageRequests = [];

export function getImage(requestParameters: MapLibreRequestParameters): MapLibreRequest<MapLibreResponse<ImageBitmap | HTMLImageElement>> {
    if (webpSupported.supported) {
        if (!requestParameters.headers) requestParameters.headers = {};
        requestParameters.headers['Accept'] = 'image/webp,*/*';
    }

    function requestMaker() {
        const request = helper.getArrayBuffer(requestParameters);

        ongoingImageRequests.push(request);
        request.response.finally(() => ongoingImageRequests.splice(ongoingImageRequests.indexOf(request), 1));

        return request;
    }

    return {
        response: (async () => {
            const response = await requestMaker().response;
            const image = await arrayBufferToCanvasImageSource(response.data);

            return {
                data: image,
                cacheControl: response.cacheControl,
                expires: response.expires
            };
        })(),

        cancel() {
            //
        }
    };
}

Осталось лишь добавить ограничение касательно запуска requestMaker: перед тем как запустить ее, нужно дождаться await выполнения всех запросов из массива:

const ongoingImageRequests = [];

export function getImage(requestParameters: MapLibreRequestParameters): MapLibreRequest<MapLibreResponse<ImageBitmap | HTMLImageElement>> {
    if (webpSupported.supported) {
        if (!requestParameters.headers) requestParameters.headers = {};
        requestParameters.headers['Accept'] = 'image/webp,*/*';
    }

    function requestMaker() {
        const request = helper.getArrayBuffer(requestParameters);

        ongoingImageRequests.push(request);
        request.response.finally(() => ongoingImageRequests.splice(ongoingImageRequests.indexOf(request), 1));

        return request;
    }

    return {
        response: (async () => {
            await Promise.allSettled(ongoingImageRequests.map(ongoingImageRequest => ongoingImageRequest.response));

            const response = await requestMaker().response;
            const image = await arrayBufferToCanvasImageSource(response.data);

            return {
                data: image,
                cacheControl: response.cacheControl,
                expires: response.expires
            };
        })(),

        cancel() {
            //
        }
    };
}

Я понимаю это так: при начале выполнения getImage (она вызвана откуда-то извне), она сразу же возвращает объект, в котором response - это Promise, который разрешится как минимум не раньше того момента, когда будут выполнены все остальные запросы из очереди.

Но, как выходит, такое решение почему-то не работает. Вопрос - почему? И как сделать так, чтобы оно работало? Хотя бы для n === 1.

Ответы

Ответов пока нет.