Soru Gecikenine ekli geri çağrıları olan bir jquery ajax isteğini tekrar deneyin.


Geçici bir nedenden dolayı başarısız olan ajax isteklerini yeniden deneyen bir sistemi uygulamaya çalışıyorum. Benim durumumda, oturumu yeniden canlandıran bir yenileme servisini çağırdıktan sonra, oturumun süresi dolduğundan 401 durum koduyla başarısız olan istekleri yeniden denemekle ilgilidir.

Sorun şu ki, "done" geri aramaları, çağrılan "başarılı" ajax seçenek geri çağırmasından farklı olarak, başarılı bir yeniden denemede çağrılmamaktadır. Aşağıda basit bir örnek oluşturdum:

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

$.ajax({
    url: '/inexistent_url',
    success: function() { alert('success'); }
})
.done(function() {
    alert('done');
});

Başarılı bir yeniden deneme için çağrıda bulunulmuş geri çağırmalara sahip olmanın bir yolu var mı? Bir ertelemenin 'reddedildikten' sonra 'çözülemeyeceğini' biliyorum, reddetmeyi önlemek mümkün mü? Ya da orijinal ertelenen doneList'i yeni bir ertelenebilir mi? Fikirlerim tükendi :)

Aşağıda, 401 reddedilen tüm istekleri sıraya koymaya ve başarılı bir görüşme / yenilemenin ardından bunları yeniden denemeye çalıştığım daha gerçekçi bir örnek.

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

Mekanizma işe yarıyor, 401 başarısız istekleri ikinci kez kovuyor, sorun onların 'bitti' geri çağırmalara çağrı yapılmıyor, bu yüzden uygulamalar duruyor.


36
2017-08-03 09:56


Menşei


Bir giriş diyaloğunu tetiklemek için 401’leri kullanan bir Tek Sayfa Uygulamasını uygularken aynı problemi yaşamak. Kaynağa baktım ve sanırım Deferred'lerle çok hızlı bir şekilde çözülürken yapmak imkansız. - Dave Van den Eynde
jsfiddle.net/8AgEjBenim için çalışıyor ... tam olarak ne var? - Never Back Down
olası kopyası JQuery'yi kullanarak bir AJAX isteğini yeniden denemenin en iyi yolu nedir? - user2284570


Cevaplar:


Kullanabilirsin jQuery.ajaxPrefilter jqXHR'yi diğerine sarmak ertelenmiş nesne.

Bir örnek verdim jsFiddle Bu, çalışmayı gösterir ve kodunuzun bir kısmını 401'i bu sürüme taşımak için uyarlamaya çalıştı:

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

Bu çünkü deferred.promise(object) Aslında jqXHR'deki "vaat yöntemlerinin" tümünü geçersiz kılar.

NOT: Bunu bulmak için başkasına, eğer success: ve error: ajax seçeneklerinde bu snippet olacak değil beklediğiniz gibi çalışın. Tek geri çağrıların, .done(callback) ve .fail(callback) jqXHR yöntemleri.


48
2017-09-16 11:07



Bu çözümü beğendim. Her $ .get ve $ .post çağrısına girip değiştirmemi engelliyor. - Dave Van den Eynde
Bu nesnenin ajax tarafından nasıl işlendiğini gösteren yeniden denemeyi seçmektense, muhtemelen $ .extend ({}, originalOpts, {refreshRequest: true}) gibi bir şeyi kullanmanız daha iyi olur. - Julian Aubourg
Teşekkürler @JulianAubourg - Tüm bu sihir ertelenmiş / ajax'ın yazarının bir değişiklik önerdiği zaman - bunu yaparsınız;) - gnarf
Bu kullanımı çok beğendim - bunu bilmiyordum. FWIW başarısız işleyicisinde, bunun yerine jqXHR kullanmak isteyebilirsiniz, aksi takdirde bağlam seçeneği kullanıldıysa bu kırılır - dherman
@Ciantic Hala bir şey arıyorsanız, sözler ve özel yeniden deneme mantığı da dahil olmak üzere tam API desteğini denemek için bu çözüm üzerinde genişledim - plugins.jquery.com/jquery.ajaxRetry/0.1.2 - dherman


Gnarf'ın cevap notları gibi, başarı ve hata geri bildirimleri beklendiği gibi davranmayacaktır. Burada ilgilenen herkesin her ikisini de destekleyen bir versiyonudur. success ve error geri çağrılar yanı sıra vaat tarzı olaylar.

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

15
2017-10-11 13:25



Bunu kısaca test ettim, ama şu ana kadar kusursuz çalışıyor. Bu fantastik. Artık bir kullanıcının oturumu bir formu göndermeden önce sona erdiğinde, tekrar oturum açmak için bir istek alırlar ve ardından formları başarıyla giriş yaptıktan sonra gönderilir. Daha fazla veri kaybı olmaz! - mpen
Bu harika, harika kod! Bunun için çok teşekkürler - Michael Rashkovsky


Bir tane yarattım jQuery eklentisi Bu kullanım durumu için. Gnarf'ın cevabında açıklanan mantığı bir eklentiye sarar ve ek olarak ajax çağrısını tekrar denemeden önce beklemek için bir zaman aşımı belirtmenize izin verir. Örneğin.

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

9
2017-09-16 19:49



Güzel, ama hala AJAX çağrılarımın her çağrı sitesinde nasıl ele alınacağını belirtmek zorundayım. - Dave Van den Eynde
+1 - Hedef basit bir deneme ise, bu eklenti sağlam. Ajax isteklerini genişletme API'sini gerçekten çok seviyorum .retry(times) - Ayrıca, temizlemeyi / daha verimli hale getirmek için birkaç tane istek gönderdim :) - gnarf
Evet, eklentiyi de seviyorum. Bence ajaxPrefilter bu özel kullanım durumunda daha iyi bir yaklaşım. - Julian Aubourg
Yapılan geri arama yeniden denemek mümkün mü? Örneğin. bittiğinde geri arama veri özelliği alır error değerle "LOGIN_REQUIRED" Talep üzerine tekrar denemek istiyorum. Yeniden denenmesi ya da denenmesi için mantık bitmiş ya da geri arama başarısız olmalıdır ... Başım ağrıyor. - Ciantic
Bir süredir biliyorum, ama bu harika bir eklenti! - Jason Washo


Böyle bir şey senin için çalışır mı? Sadece kendi ertelenmiş / Sözünü geri vermelisiniz, böylece orijinali çok yakında reddedilmeyecek.

Örnek / test kullanımı: 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();
}

6
2017-09-13 03:16



Dün gece bu şekilde çözdüm, bundan biraz daha ayrıntılı olsa da. @Cipak bunu doğru cevap olarak kabul ederse, ödülü vereceğim. - Dave Van den Eynde
Neden bu rastgele iç IIFE? İçinde herhangi bir değişken / işlev belirtmiyorsunuz. - gnarf
@gnarf: Bunu çalıştırmak için bir yol olarak kullanıyor makeRequest Hem hemen, hem de daha sonra hata işleyicisinin içinde. - Matt
oops, darn iç referansı kaçırdı - teşekkürler @Matt - gnarf
401 durum kodu geri döndürülürse bu sonsuz bir döngüye (ve dolayısıyla kendinden DDOS atağına) neden olmaz mıydı? - Mike Sherov


Bu bir harika Ben de sadece karşı karşıya olduğum soru.

Kabul edilen cevaptan korktum (@gnarf'dan), bu yüzden daha kolay anladığım bir şekilde anladım:

        var retryLimit = 3;
        var tryCount = 0;
        callAjax(payload);
        function callAjax(payload) {
            tryCount++;
            var newSaveRequest = $.ajax({
                url: '/survey/save',
                type: 'POST',
                data: payload,
                headers: {
                    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                },
                error: function (xhr, textStatus, errorThrown) {
                    if (textStatus !== 'abort') {
                        console.log('Error on ' + thisAnswerRequestNum, xhr, textStatus, errorThrown);
                        if (tryCount <= retryLimit) {
                            sleep(2000).then(function () {
                                if ($.inArray(thisAnswerRequestNum, abortedRequestIds) === -1) {
                                    console.log('Trying again ' + thisAnswerRequestNum);
                                    callAjax(payload);//try again
                                }
                            });
                            return;
                        }
                        return;
                    }
                }
            });
            newSaveRequest.then(function (data) {
                var newData = self.getDiffFromObjects(recentSurveyData, data);
                console.log("Answer was recorded " + thisAnswerRequestNum, newData);//, data, JSON.stringify(data)
                recentSurveyData = data;
            });
            self.previousQuizAnswerAjax = newSaveRequest;
            self.previousQuizAnswerIter = thisAnswerRequestNum;
        }


function sleep(milliseconds) {
    return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

Temel olarak, tüm Ajax çağrısını tamamladım ve geri çağrılanlar, özyinelemeli olarak çağrılabilen tek bir işleve dönüştürülür.


0
2018-03-01 21:16