MaxLevs
@MaxLevs

Как буфферизировать ajax-запросы в async-функциях?

Доброго времени суток.
Пишу юзерскрипт для себя - более удобная читалка
Что за скрипт
(страницы - изображения; для каждой страницы есть комментарии; выстраиваю страницы книги на одной странице сайта, при желании можно заглянуть в комментарии к любой странице: почитать, прокомментировать, читать удобнее);

Чтобы сильно не грузить страницу загрузкой своего юзерскрипта делаю все в асинхронных функциях.

Проблема такая, форма запрашивается тоже через ajax {произведение, глава} (в оригинальном исполнении перед каждой отправкой комментария запрашивается одинаковая форма). В скрипте я сразу подгоняю форму под каждую страницу.
Как не слать кучу запросов для получения формы? Понятно, что нужно как-то буферизировать запрос получения формы, а затем отдавать лишь результат. Но функции асинхронные, и перед тем, как ответ придёт, будет отправлено несколько запросов, что может привести к бану.

Разделил запрос формы для страницы на 3 этапа:
  1. Запрос формы с сервера [1 раз; ограничение на кол-во запросов в сек]
  2. Косметические изменения [1 раз; изменение общего поведения формы]
  3. Адаптация формы под конкретную страницу [Множество раз]


Сейчас есть что-то на подобие этого:
const getCommentsInput = (function () {
        let commInputBlock = null;
        let inition = false;
        let commentForm = null;

        // Инициализация формы
        async function initForm () {
            console.log("Inition called!");
            inition = true;
            const mangaId = $(".reader-controls").html().match(/mangaId=(\d+)/)[1];
            const chapterId = $(".reader-controls").html().match(/chapterId=(\d+)/)[1];
            commentForm = $.ajax({
                 type:'POST'
                ,data:{'mangaId': mangaId,'chapterId': chapterId}
                ,url:'/internal/modal/addTwittInline'
            });
            commInputBlock = $("<div>").html(await commentForm);
            const frm = commInputBlock.find("form");
            frm.attr("onsubmit", frm.attr("onsubmit").replace(".val(rm_h.cur)", ""));
        }

        // Замыкание для генерации блока формы
        return async function (key) {
            if (!inition) {await initForm();} // Инициализируем форму при первом запросе
            else await commentForm; // Одна из моих попыток ждать конца запроса
            const newCommentBlock = commInputBlock.clone();
            newCommentBlock.find("#twittPageNumHidden").attr({value: key});
            return newCommentBlock.attr({id: `com_input_${key}`});
        };
    })();


Можно было бы хранить результат вызова initForm() в замыкании и отдавать по первому требованию. При чем возвращать промис: если форма уже получена и подкорректирована, то промис разрешится мгновенно, если нет, то просто await его.
Но тут и так уже куча замыканий в коде, + хранение промиса и возвращение его из асинхронной функции потребует двойного await...

Чувствую иду куда-то не туда. Подскажите, как в буфферизируете большое количество ассинхронных сетевых запросов. Нужно избавиться от повторения, дабы не схлопотать бан.
  • Вопрос задан
  • 21 просмотр
Пригласить эксперта
Ответы на вопрос 1
MaxLevs
@MaxLevs Автор вопроса
Всё, сделал. (НО не всё понял)
Можно было сразу догадаться
Как я и думал, проблема в том, что я слишком сильно начал зарываться в замыкания. Из-за длинных цепочек замыканий стало сложно ориентироваться в коде и сказать, как правильно применить async/await (как поведёт себя async/await). Видимо, я всё ещё не до конца осознал механизмы работы async/await, помнил, но не осознал, запамятовал, что async возвращает промис ещё до return (Помнил, но не "дотягивался", банальная мысль).
Как только я осознал, что воюю с async не в те ворота, то всё сразу встало на свои места. Можно было бы потратить пару дней на реализацию подхода с цепочками рекурсии, чтобы вдолбить механизмы его работы себе в голову, но решил не забивать гвозди микроскопом. Раз уж я "дотянулся" до верного механизма async и цепочка действий легла на это поле, то можно просто сделать.


В моём случае, не нужно ждать, пока появится первый запрос на формирование блока комментариев страницы, поэтому достаточно сделать запрос общей формы (первые 2 шага) заранее, запихнуть это в промис и доставать из замыкания уже при появлении запросов.
const getCommentsInputBlock = (function assableCommBlockGenerator() {
        // Запрос формы
        function requestForm () {
            const mangaId = $(".reader-controls").html().match(/mangaId=(\d+)/)[1];
            const chapterId = $(".reader-controls").html().match(/chapterId=(\d+)/)[1];
            return $.ajax({
                type:'POST',
                data:{'mangaId': mangaId,'chapterId': chapterId},
                url:'/internal/modal/addTwittInline',
            });
        }

        // Косметические правки поведения
        async function makeUpForm () {
            const commentFormPromise = requestForm();

            const commInputBlock = $("<div>").html(await commentFormPromise);
            const frm = commInputBlock.find("form");
            frm.attr("onsubmit", frm.attr("onsubmit").replace(".val(rm_h.cur)", ""));

            return commInputBlock;
        }

        // Запрашиваем форму заранее - все равно понадобится (промис остаётся в замыкании)
        const commInputBlockPromise = makeUpForm();

        // Замыкание для генерации блока формы
        return async function adaptateFormToPage (key) {
            let commInputBlock = await commInputBlockPromise;
            const newCommentBlock = commInputBlock.clone();
            newCommentBlock.find("#twittPageNumHidden").attr({value: key});
            return newCommentBlock.attr({id: `com_input_${key}`});
        };
    })();


Из-за этого маленького нюанса всё сводится к одному единственному замыканию. Проверка единственности запроса к базе и не нужна вовсе.

Однако я всё ещё не понимаю, как действовать, если мы не можем предугадать запрос заранее, но мы должны обеспечить асинхронность, единственность запроса и гарантировать доступ к полученным данным во всех требуемых местах при актуализации?
Очевидно будет, что выглядеть это должно так, я всё ещё не уверен во всём:
const reqData = await (async function assableRequestor() {
    const dataPromises = {};
    return async function reqData(params) {
        const prmsStr = `${params}`;
        if(!dataPromises.includes(prmsStr)) {
            const dataPromise = $.ajax(params);
            dataPromises[prmsStr] = dataPromise;
            return await dataPromise;
        }
        return await datas[prmsStr];
    };
})();

async function doSomething(params) {
    let data = await reqData(params);
    /*Модификация данных*/
    return data;
}

async function doItCool(params) {
    let data = await reqData(params);
    /*Другая Модификация данных*/
    return data;
}

setTimeout(()=>{doSomething(params1)}, 5000);
setTimeout(()=>{doItCool(params1)}, 3000);
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы