jQuery / AJAX устанавливает тайм-аут при достижении предела скорости стороннего API

В своем приложении я делаю несколько вложенных вызовов AJAX к LiquidPlanner API, который ограничивает запросы до 30 requests every 15 seconds. Когда я достигаю предела, я хочу установить своего рода тайм-аут, чтобы прекратить отправку запросов к API, пока не истекут 15 секунд. Это (на данный момент) будет использовать только один человек, поэтому несколько клиентов не являются проблемой.

При достижении предела скорости ответ будет следующим:

{
    "type":"Error",
    "error":"Throttled",
    "message":"34 requests, exceeds limit of 30 in 15 seconds. Try again in 7 seconds, or contact [email protected]"
}

Вот код, упрощенный для краткости:

$.getJSON('/dashboard/tasks/123, function(tasks) {
    $.each(tasks, function(t, task) {
        $.getJSON('/dashboard/project/987, function(project) {
            $.getJSON('/dashboard/checklist-items/382983, function(checklist-items) {
                // form some html here
            });
        });
    });
});

Таким образом, в любой момент этого процесса я могу достичь предела, и мне придется подождать, пока истечет тайм-аут.

Я также открыт для предложений по лучшему формированию запросов вместо их вложенности.


person Jared Eitnier    schedule 30.06.2015    source источник
comment
Мы не знаем, что такое API, и даже не знаем, что он возвращает при достижении ограничения скорости. Я не уверен, что мы сможем вам чем-то помочь с недостатком информации.   -  person    schedule 30.06.2015
comment
Итак, это будет обрабатывать тайм-аут от 1 клиента, а как насчет нескольких клиентов? Если вам нужно ограничить количество обращений к API, вам, вероятно, потребуется сделать это на стороне сервера, чтобы ваш сервер вызывал API и передавал результаты.   -  person Mike Brant    schedule 30.06.2015
comment
Вопрос @Jamen обновлен с этой информацией.   -  person Jared Eitnier    schedule 30.06.2015
comment
@MikeBrant несколько клиентов не будет проблемой.   -  person Jared Eitnier    schedule 30.06.2015


Ответы (3)


Другое решение, которое, вероятно, лучше предотвращает удары молотком, - это очередь, однако вы должны знать, что при использовании этого метода порядок запросов может значительно отличаться. И что только один запрос будет выполняться одновременно (поэтому общее время ответа может значительно увеличиться в зависимости от варианта использования).

//Keep track of queue
var queue = [];

//Keep track of last failed request
var failed_request = false;

function do_request(url, callback) {
    //Just add to queue
    queue.push({
        url:url,
        callback:callback
    });
    //If the queue was empty send it off
    if (queue.length === 1) attempt_fetch();
}

function attempt_fetch() {
    //If nothing to do just return
    if (queue.length === 0 && failed_request === false) return;

    //Get the url and callback from the failed request if any,
    var parms;
    if (failed_request !== false) {
        parms = failed_request;
    } else {
        //otherwise first queue element
        parms = queue.shift();
    }
    //Do request
    $.getJSON(parms.url, function(response) {
        //Detect throttling
        if (response.type === 'error' && response.error === 'throttled') {
            //Store the request
            failed_request = parms;
            //Call self in 15 seconds
            setTimeout(function(){
                attempt_fetch();
            }, 15000);
        } else {
            //Request went fine, let the next call pick from the queue
            failed_request = false;
            //Do your stuff
            parms.callback(response);
            //And send the next request
            attempt_fetch();
        }
    }
}

... ваша логика остается в основном неизменной:

do_request('/dashboard/tasks/123', function(tasks) {
    $.each(tasks, function(t, task) {
        do_request('/dashboard/project/987', function(project) {
            do_request('/dashboard/checklist-items/382983', function(checklist_items) {
                // form some html here
            });
        });
    });
});

Заявление об ограничении ответственности: все еще полностью не протестирован.

person Mikk3lRo    schedule 30.06.2015

Что касается шаблонов проектирования для объединения нескольких запросов, ознакомьтесь с разделом цепочки в следующей статье: http://davidwalsh.name/write-javascript-promises. По сути, вы можете создать службу, которая предоставляет метод для каждого типа запроса, который возвращает объект обещания, а затем при необходимости объединяет их в цепочку.

Что касается вашего вопроса об установке тайм-аута, учитывая предоставленную вами информацию, немного сложно дать вам совет по этому поводу, но если это абсолютно все, что у нас есть, я бы создал очередь запросов (простой массив, который позволяет вам запихать новые запросы в конец и выкинуть из головы). Затем я выполнял известные запросы по порядку и проверял ответ. Если ответ был ошибкой тайм-аута, установите флаг тайм-аута, который будет учтен исполнителем запроса, и в случае успеха либо поставьте в очередь дополнительные запросы, либо создайте вывод html. Вероятно, это довольно плохой дизайн, но это все, что я могу предложить, учитывая предоставленную вами информацию.

person GPicazo    schedule 30.06.2015

Напишите оболочку, которая обнаружит ответ с ограниченной скоростью:

//Keep track of state
var is_throttled = false;

function my_wrapper(url, callback) {
    //No need to try right now if already throttled
    if (is_throttled) {
        //Just call self in 15 seconds time
        setTimeout(function(){
            return my_wrapper(url, callback);
        }, 15000);
    }
    //Get your stuff
    $.getJSON(url, function(response) {
        //Detect throttling
        if (response.type === 'error' && response.error === 'throttled') {
            /**
             * Let "others" know that we are throttled - the each-loop
             * (probably) makes this necessary, as it may send off
             * multiple requests at once... If there's more than a couple
             * you will probably need to find a way to also delay those,
             * otherwise you'll be hammering the server before realizing
             * that you are being limited
             */
            is_throttled = true
            //Call self in 15 seconds
            setTimeout(function(){
                //Throttling is (hopefully) over now
                is_throttled = false;
                return my_wrapper(url, callback);
            }, 15000);
        } else {
            //If not throttled, just call the callback with the data we have
            callback(response);
        }
    }
}

Тогда вы сможете переписать свою логику так:

my_wrapper('/dashboard/tasks/123', function(tasks) {
    $.each(tasks, function(t, task) {
        my_wrapper('/dashboard/project/987', function(project) {
            my_wrapper('/dashboard/checklist-items/382983', function(checklist_items) {
                // form some html here
            });
        });
    });
});

Заявление об ограничении ответственности: Полностью непроверено - меня больше всего беспокоит область применения url и _4 _... Но вам, вероятно, проще протестировать.

person Mikk3lRo    schedule 30.06.2015