2012-08-03 17 views
34

से कॉलबैक संलग्न हैं I अस्थायी कारणों से विफल होने वाले AJAX अनुरोधों को पुनः प्रयास करने की एक प्रणाली को लागू करने का प्रयास कर रहा हूं। मेरे मामले में, यह उन अनुरोधों को पुनः प्रयास करने के बारे में है जो 401 स्थिति कोड के साथ विफल रहे क्योंकि सत्र को समाप्त करने के बाद सत्र को समाप्त करने के बाद सत्र समाप्त हो गया है।एक jquery AJAX अनुरोध का पुनः प्रयास करें जिसमें इसके स्थगित

समस्या यह है कि "सफलता" AJAX विकल्प कॉलबैक के विपरीत, "पूर्ण" कॉलबैक को एक सफल पुनः प्रयास पर नहीं कहा जाता है। किया शैली कॉलबैक एक सफल पुन: प्रयास पर कहा जाता है के लिए

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

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

वहाँ एक रास्ता है: मैं नीचे एक सरल उदाहरण बना हुआ है? मुझे पता है कि स्थगित 'अस्वीकार' के बाद 'हल' किया जा सकता है, क्या यह अस्वीकार करना संभव है? या हो सकता है कि मूल स्थगित किए गए मूल की प्रतिलिपि को एक नए स्थगित कर दें? मैं विचारों से बाहर हूं :)

नीचे एक और यथार्थवादी उदाहरण, जहां मैं सभी 401-अस्वीकृत अनुरोधों को कतारबद्ध करने का प्रयास कर रहा हूं, और सफल कॉल/रीफ्रेश के बाद उन्हें पुनः प्रयास कर रहा हूं।

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

तंत्र काम करता है, 401-असफल अनुरोध को दूसरी बार निकाल दिया, समस्या उनकी 'पूर्ण' कॉलबैक कहा जाता है नहीं मिलता है, तो अनुप्रयोगों स्टालों।

+1

एक एकल पृष्ठ एप्लिकेशन को लागू करते समय एक ही समस्या होने पर जो लॉगिन संवाद को ट्रिगर करने के लिए 401 का उपयोग करता है। मैंने स्रोत को देखा है और मुझे लगता है कि डिफर्रेड के साथ ऐसा करना असंभव है क्योंकि वे बहुत जल्दी हल हो जाते हैं। –

+0

http://jsfiddle.net/8AgEj/ यह मेरे लिए काम कर रहा है ... सही समस्या क्या है? –

+0

संभव डुप्लिकेट [jQuery का उपयोग कर विफलता पर AJAX अनुरोध को पुनः प्रयास करने का सबसे अच्छा तरीका क्या है?] (Http://stackoverflow.com/questions/10024469/whats-the-best-way-to-retry-an-ajax-request -ऑन-विफलता-उपयोग-jquery) – user2284570

उत्तर

44

आप jQuery.ajaxPrefilter का उपयोग एक और deferred object में 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); 
}); 

यह काम करता है क्योंकि deferred.promise(object) वास्तव में "वादा के सभी के ऊपर लिख देगा विधियों "jqXHR पर।

नोट: किसी और को करने के लिए, यह लग रहा है, तो आप success: और ajax विकल्पों में error: साथ कॉलबैक संलग्न कर रहे हैं, इस स्निपेट होगा नहीं काम जिस तरह से आप उम्मीद करते हैं। यह मानता है कि jqXHR के .done(callback) और .fail(callback) विधियों का उपयोग करके केवल कॉलबैक संलग्न किए गए हैं।

+1

लगाए गए किसी प्रकार की सीमा होनी चाहिए मुझे यह समाधान पसंद है। यह मुझे हर $ .get और $। पोस्ट कॉल को बदलने और बदलने से रोकता है। –

+0

आप शायद इस ऑब्जेक्ट को एजेक्स द्वारा संसाधित करने के तरीके के बाद रेट्री के विकल्प के बजाय $ .extend ({}, originalOpts, {refreshRequest: true}) जैसे कुछ बेहतर उपयोग करेंगे। –

+0

धन्यवाद @ जुलिएनऑबॉर्ग - जब इस जादू के स्थगित/AJAX के लेखक एक परिवर्तन का सुझाव देते हैं - आप इसे बनाते हैं;) – gnarf

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(); 
} 
+0

इस तरह मैंने कल रात इसे हल किया, हालांकि इससे थोड़ा अधिक विस्तृत। यदि @cipak इसे सही उत्तर के रूप में स्वीकार करता है, तो मैं बक्षीस दूंगा। –

+0

क्यों यादृच्छिक आंतरिक IIFE? आप इसके अंदर किसी भी चर/कार्यों को स्कॉप नहीं कर रहे हैं। – gnarf

+0

@gnarf: वह इसे तुरंत 'makeRequest' चलाने के लिए एक तरीके के रूप में उपयोग कर रहा है, और फिर फिर त्रुटि हैंडलर के अंदर। – Matt

9

मैंने इस उपयोग के मामले के लिए jQuery plugin बनाया है। यह प्लगइन में 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!"); 
}); 
+0

अच्छा, लेकिन मुझे अभी भी यह निर्दिष्ट करना होगा कि प्रत्येक कॉल साइट पर मेरे AJAX कॉल को कैसे प्रबंधित किया जाना चाहिए। –

+1

+1 - यदि लक्ष्य एक साधारण पुनः प्रयास है, तो यह प्लगइन ठोस है। मुझे वास्तव में '.retry (times)' के साथ AJAX अनुरोधों को विस्तारित करने की एपीआई पसंद है - इसके अलावा मैंने इसे साफ़ करने के लिए कुछ पुल अनुरोध सबमिट किए हैं/इसे और अधिक कुशल बना दिया है :) – gnarf

+0

हाँ, मुझे प्लगइन भी पसंद है। मुझे लगता है कि AJAXPrefilter हालांकि इस विशिष्ट उपयोग-मामले में एक बेहतर दृष्टिकोण है। –

13

जैसा कि gnarf के उत्तर नोट्स, सफलता और त्रुटि कॉलबैक अपेक्षित व्यवहार नहीं करेंगे। यदि कोई यहां रुचि रखता है तो वह संस्करण है जो success और error कॉलबैक के साथ-साथ वादे शैली की घटनाओं दोनों का समर्थन करता है।

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

मैंने केवल संक्षेप में इसका परीक्षण किया है, लेकिन अब तक यह बेकार ढंग से काम कर रहा है। यह शानदार है। अब जब कोई उपयोगकर्ता एक फॉर्म सबमिट करने से पहले समाप्त हो जाता है तो उन्हें दोबारा साइन इन करने का संकेत मिलता है, और फिर उनके फॉर्म को सफलतापूर्वक लॉग इन करने के तुरंत बाद सबमिट किया जाता है। कोई और खोया डेटा नहीं! – mpen

+1

यह अद्भुत है, महान कोड! इस लिए आपका बहुत - बहुत धन्यवाद –

0

यह एक महान सवाल है कि मैं अभी भी सामना करना पड़ा है।

मैं स्वीकार किए जाते हैं जवाब (@gnarf से) से भयभीत था, तो मैं एक तरीका है कि मैं आसान समझा पता लगा:

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

असल में, मैं सिर्फ पूरे अजाक्स कॉल और अपनी कॉलबैक लिपटे एक समारोह में जिसे रिकर्सिवली कहा जा सकता है।

संबंधित मुद्दे