重试jquery ajax请求,该请求附加了延迟的回调

cip*_*pak 37 ajax jquery jquery-deferred

我正在尝试实现一个重试ajax请求的系统,这些请求因临时原因而失败.在我的例子中,它是关于在调用刷新web服务以恢复会话之后重试因401会议状态代码而失败的请求,因为会话已过期.

问题是成功重试时不会调用"done"回调,这与调用的"success"ajax选项回调不同.我在下面编写了一个简单的例子:

$.ajaxSetup({statusCode: {
    404: function() {
        this.url = '/existent_url';
        $.ajax(this);
    }
}});

$.ajax({
    url: '/inexistent_url',
    success: function() { alert('success'); }
})
.done(function() {
    alert('done');
});
Run Code Online (Sandbox Code Playgroud)

是否有办法在成功重试时调用完成式回调?我知道延期不能被"拒绝"后"解决",是否有可能阻止拒绝?或者可能将原始延迟的doneList复制到新的延迟?我没有想法:)

下面是一个更现实的例子,我试图排队所有401拒绝的请求,并在成功调用/ refresh后重试它们.

var refreshRequest = null,
    waitingRequests = null;

var expiredTokenHandler = function(xhr, textStatus, errorThrown) {

    //only the first rejected request will fire up the /refresh call
    if(!refreshRequest) {
        waitingRequests = $.Deferred();
        refreshRequest = $.ajax({
            url: '/refresh',
            success: function(data) {
                // session refreshed, good
                refreshRequest = null;
                waitingRequests.resolve();
            },
            error: function(data) {
                // session can't be saved
                waitingRequests.reject();
                alert('Your session has expired. Sorry.');
            }
       });
    }

    // put the current request into the waiting queue
    (function(request) {
        waitingRequests.done(function() {
            // retry the request
            $.ajax(request);
        });
    })(this);
}

$.ajaxSetup({statusCode: {
    401: expiredTokenHandler
}});
Run Code Online (Sandbox Code Playgroud)

该机制工作,401失败的请求第二次被触发,问题是他们的'完成'回调没有被调用,所以应用程序停止.

gna*_*arf 50

您可以使用jQuery.ajaxPrefilter将jqXHR包装在另一个延迟对象中.

我做了一个示例jsFiddle,显示它正常工作,并尝试调整一些代码来处理401到这个版本:

$.ajaxPrefilter(function(opts, originalOpts, jqXHR) {
    // you could pass this option in on a "retry" so that it doesn't
    // get all recursive on you.
    if (opts.refreshRequest) {
        return;
    }

    // our own deferred object to handle done/fail callbacks
    var dfd = $.Deferred();

    // if the request works, return normally
    jqXHR.done(dfd.resolve);

    // if the request fails, do something else
    // yet still resolve
    jqXHR.fail(function() {
        var args = Array.prototype.slice.call(arguments);
        if (jqXHR.status === 401) {
            $.ajax({
                url: '/refresh',
                refreshRequest: true,
                error: function() {
                    // session can't be saved
                    alert('Your session has expired. Sorry.');
                    // reject with the original 401 data
                    dfd.rejectWith(jqXHR, args);
                },
                success: function() {
                    // retry with a copied originalOpts with refreshRequest.
                    var newOpts = $.extend({}, originalOpts, {
                        refreshRequest: true
                    });
                    // pass this one on to our deferred pass or fail.
                    $.ajax(newOpts).then(dfd.resolve, dfd.reject);
                }
            });

        } else {
            dfd.rejectWith(jqXHR, args);
        }
    });

    // NOW override the jqXHR's promise functions with our deferred
    return dfd.promise(jqXHR);
});
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为它deferred.promise(object)实际上会覆盖jqXHR上的所有"promise方法".

注意:对于发现此问题的任何其他人,如果您使用success:error:在ajax选项中附加回调,则此代码段将无法按预期方式工作.它假设唯一的回调是使用jqXHR 的.done(callback).fail(callback)方法附加的回调.

  • @Ciantic如果你还在寻找什么,我已经扩展了这个解决方案,尝试完整的API支持,包括promises和自定义重试逻辑 - http://plugins.jquery.com/jquery.ajaxRetry/0.1.2/ (3认同)

rya*_*yan 15

正如gnarf的答案所指出的那样,成功和错误回调将不会像预期的那样表现.如果有人有兴趣在这里是既支持版本successerror回调以及承诺的风格事件.

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {

    // Don't infinitely recurse
    originalOptions._retry = isNaN(originalOptions._retry)
        ? Common.auth.maxExpiredAuthorizationRetries
        : originalOptions._retry - 1;

    // set up to date authorization header with every request
    jqXHR.setRequestHeader("Authorization", Common.auth.getAuthorizationHeader());

    // save the original error callback for later
    if (originalOptions.error)
        originalOptions._error = originalOptions.error;

    // overwrite *current request* error callback
    options.error = $.noop();

    // setup our own deferred object to also support promises that are only invoked
    // once all of the retry attempts have been exhausted
    var dfd = $.Deferred();
    jqXHR.done(dfd.resolve);

    // if the request fails, do something else yet still resolve
    jqXHR.fail(function () {
        var args = Array.prototype.slice.call(arguments);

        if (jqXHR.status === 401 && originalOptions._retry > 0) {

            // refresh the oauth credentials for the next attempt(s)
            // (will be stored and returned by Common.auth.getAuthorizationHeader())
            Common.auth.handleUnauthorized();

            // retry with our modified
            $.ajax(originalOptions).then(dfd.resolve, dfd.reject);

        } else {
            // add our _error callback to our promise object
            if (originalOptions._error)
                dfd.fail(originalOptions._error);
            dfd.rejectWith(jqXHR, args);
        }
    });

    // NOW override the jqXHR's promise functions with our deferred
    return dfd.promise(jqXHR);
});
Run Code Online (Sandbox Code Playgroud)


joh*_*aul 9

我为这个用例创建了一个jQuery插件.它在插件中包含了gnarf的答案中描述的逻辑,并且还允许您指定在再次尝试ajax调用之前等待的超时.例如.

//this will try the ajax call three times in total 
//if there is no error, the success callbacks will be fired immediately
//if there is an error after three attempts, the error callback will be called

$.ajax(options).retry({times:3}).then(function(){
  alert("success!");
}); 

//this has the same sematics as above, except will 
//wait 3 seconds between attempts
$.ajax(options).retry({times:3, timeout:3000}).retry(3).then(function(){
   alert("success!");
});  
Run Code Online (Sandbox Code Playgroud)

  • +1 - 如果目标是简单的重试,则此插件是可靠的.我真的很喜欢使用`.retry(times)`扩展ajax请求的API - 我还提交了一些pull请求来清理它/使它更有效:) (2认同)

dhe*_*man 6

这样的事能为你效劳吗?您只需要返回自己的延期/承诺,以便原来的不会过早拒绝.

示例/测试用法:http: //jsfiddle.net/4LT2a/3/

function doSomething() {
    var dfr = $.Deferred();

    (function makeRequest() {
        $.ajax({
            url: "someurl",
            dataType: "json",
            success: dfr.resolve,
            error: function( jqXHR ) {
                if ( jqXHR.status === 401 ) {
                    return makeRequest( this );
                }

                dfr.rejectWith.apply( this, arguments );
            }
        });
    }());

    return dfr.promise();
}
Run Code Online (Sandbox Code Playgroud)

  • 如果401状态代码一直被返回,这不会导致无限循环(因此是自我DDOS攻击)吗? (2认同)