Страницы

Поиск по вопросам

воскресенье, 15 декабря 2019 г.

Как поставить вызовы асинхронной ф-ии в очередь, и не чаще 3 в секунду?

#javascript #алгоритм #async_programming


Асинхронная ф-я может вызываться из разных мест большого кода, с передачей коллбэка
для возврата результата.

Есть ограничение: нельзя вызывать эту ф-ю чаще 3 раз в секунду.

Не могу сообразить, как проще сделать очередь задач.

Сделал два буфера: массив времён последних трёх вызовов times и массив объектов заданий
tasks, где каждый содержит параметры вызова и callback.

Перед выполнением очередного вызова необходимая задержка определяется как неотрицательная
разность текущего времени c моментом times[0]. После выполнения вызова, текущее время
заносится в хвост: times.push(ts); times = times.slice(-3).

Ф-я вызывается с необх. таймаутом либо по поступлении первого объекта в пустую очередь,
либо по завершении очередного задания из очереди.

Есть ли в каком-нибудь JS фреймворке похожий функционал, чтобы списать, и правильно
ли я подхожу к задаче перестроения асинхронных вызовов в последовательность?

App.timeSpan = 1000;
App.times = [];
App.tasks = [];

// точка входа
App.api = function( method, params, callback) {
    var toWait, dt, ts = (new Date()).getTime();

    this.tasks.push({
        method: method,
        params: params,
        callback: callback
    });

    if( this.tasks.length == 1) {
        if( this.times.length > 2) {
            dt = ts - this.times[0];
            toWait = ( dt < this.timeSpan) ? this.timeSpan - dt : 0;
        } else {
            toWait = 0;
        }
        window.setTimeout(this.execute.bind(this), toWait);
    }
}

App.execute = function(){
    if( this.tasks.length == 0) return;

    // вызов ТОЙ ф-ии
    VK.api( this.tasks[0].method, this.tasks[0].params, this.executed.bind(this));
}

App.executed = function(r){
    var ts = (new Date()).getTime() // timestamp in seconds
        ,dt
        ,toWait
    ;

    this.times.push( ts);
    this.times = this.times.slice(-3);

    if( this.times.length > 2) {
        dt = ts - this.times[0];
        toWait = ( dt < this.timeSpan) ? this.timeSpan - dt : 0;
    } else {
        toWait = 0;
    }

    if( this.tasks.length > 1) {
        window.setTimeout(this.execute.bind(this), toWait);
    }

    this.tasks.shift().callback.call(this, r);
}

    


Ответы

Ответ 1



По-моему, радикально упростить код не получится. Получилось лишь сделать его чуть более "красивым": //for tests function mock(m, p, callback) { var time = Math.floor(Math.random() * 300); setTimeout(function() { callback(m + ' ' + p); }, time); } //for tests function callbackMock(r) { console.log("Callbacked: " + r); } function App() { } App.callsPerTimeSpan = 3; App.timeSpan = 1000; App.times = []; App.tasks = []; App.handling = false; App.taskHandler = mock; //mock - for tests App.api = function(method, params, callback) { this.tasks.push({ method: method, params: params, callback: callback }); if (!this.handling) { this.handling = true; this.execute(); } }; App.execute = function() { if (this.tasks.length == 0) { this.handling = false; return; } var toWait = 0; if (this.times.length == this.callsPerTimeSpan) { var ts = this.getCurrentTime(); var dt = ts - this.times[0]; toWait = (dt < this.timeSpan) ? this.timeSpan - dt : 0; } var task = this.tasks[0]; var that = this; window.setTimeout(function() { that.taskHandler(task.method, task.params, that.executed.bind(that)); }, toWait); }; App.executed = function(r) { var ts = this.getCurrentTime(); this.times.push(ts); this.times = this.times.slice(-this.callsPerTimeSpan); var task = this.tasks.shift(); var that = this; setTimeout(function() { task.callback.call(that, r); }, 0); //async call this.execute(); }; App.getCurrentTime = function() { return (new Date()).getTime(); }; //for tests for (var i = 0; i < 10; i++) { App.api(String.fromCharCode(i + 65), i, callbackMock); } "Красота" в первую очередь включает в себя отсутствующий дублированный код расчета времени и исполнения следующей задачи. Пример с выводом отладочной информации в fiddle.

Ответ 2



Есть ли в каком-нибудь JS фреймворке похожий функционал Есть. В jQuery точно есть throttle и debounce, в других тоже что-то похожее. Вообще, гуглится замечательно. Да и реализуется несложно. Например, вот расширенная версия с комментариями. там «проглатываются» лишние вызовы функции. В моей задаче этого нельзя допускать Тогда так: function queue(f, ms) { var calls = null; return function () { if (calls == null) { calls = []; setTimeout(function go() { // Гарантируем асинхронный вызов if (calls.length) { f.apply(null, calls.shift()) setTimeout(go, ms); } else { calls = null; } }); } calls.push(arguments); } } Проверка: f = queue(function () { console.log(new Date, arguments) }, 1000) f(1); f(1, 2); f({}); setTimeout(f, 3500); setTimeout(f, 4500, [4, 5], "q"); setTimeout(f, 6000, 6); setTimeout(f, 8000, 8); setTimeout(f, 8500, 8.5); setTimeout(f, 9000, 9); setTimeout(f, 11000, 9);

Комментариев нет:

Отправить комментарий