jQuery/AJAX 在达到第 3 方 API 的速率限制时设置超时

jQuery/AJAX set timeout when rate limit of 3rd party API is reached

在我的应用程序中,我对 LiquidPlanner API 进行了多次嵌套 AJAX 调用,将请求限制为 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 support@liquidplanner.com"
}

这是一些代码,为简洁起见进行了简化:

$.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
            });
        });
    });
});

所以在这个过程中的任何时候我都可能达到限制并且需要等到超时完成。

我也乐于接受更好地形成请求而不是嵌套请求的建议。

至于链接多个请求的设计模式,请查看以下文章中的链接部分:http://davidwalsh.name/write-javascript-promises。基本上,您可以创建一个服务,为每种类型的请求公开一个方法,returns 承诺对象,然后根据需要将它们链接在一起。

就您关于设置超时的问题而言,鉴于您提供的信息,很难就此向您提供建议,但如果这绝对是我们所拥有的,我会创建一个请求队列(一个简单的允许您在末尾推送新请求并从头部弹出的数组)。然后我会按顺序执行已知请求并检查响应。如果响应是超时错误,则设置请求执行程序将遵守的超时标志,如果成功,则将其他请求排队或创建 html 输出。这可能是一个非常糟糕的设计,但鉴于您提供的信息,我只能提供这些。

编写一个包装器来检测限速响应:

//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
            });
        });
    });
});

免责声明: 完全未经测试 - 我主要关心的是 urlcallback 的范围...但您可能更容易测试.

另一种可能更好地防止锤击的解决方案是队列 - 但是您需要注意,使用此方法时请求的顺序可能会有很大不同。并且一次只有一个请求 运行(因此总响应时间可能会显着增加,具体取决于用例)。

//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
            });
        });
    });
});

免责声明:仍然完全未经测试。