Ponów żądanie jQuery ajax, które ma połączenia zwrotne dołączone do odroczonego
Próbuję zaimplementować system ponownej próby żądań ajax, które nie powiodą się z tymczasowego powodu. W moim przypadku chodzi o ponowne sprawdzenie żądań, które nie powiodły się z kodem stanu 401, ponieważ sesja wygasła, po wywołaniu usługi odświeżającej, która ożywia sesję.
Problem polega na tym, że" gotowe "wywołań zwrotnych nie są wywoływane na udanej ponownej próbie, w przeciwieństwie do" sukces " ajax opcja callback, który jest wywoływany. Stworzyłem prosty przykład poniżej:
$.ajaxSetup({statusCode: {
404: function() {
this.url = '/existent_url';
$.ajax(this);
}
}});
$.ajax({
url: '/inexistent_url',
success: function() { alert('success'); }
})
.done(function() {
alert('done');
});
Czy jest sposób na wywołania zwrotne w stylu "gotowe" wywołały udaną ponowną próbę? Wiem, że odroczenie nie może być "rozwiązane" po "odrzuceniu", czy można zapobiec odrzuceniu? A może skopiować oryginał odroczony na nowy odroczony? Skończyły mi się pomysły:)
Bardziej realistyczny przykład poniżej, gdzie próbuję ustawić w kolejce wszystkie odrzucone żądania 401 i ponowić je po pomyślnym wywołaniu do / 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
}});
Mechanizm działa, żądania 401-failed są odpalane po raz drugi, problem jest ich "gotowe" wywołania zwrotne nie są wywoływane, więc aplikacje przestają działać.
5 answers
You could use jQuery.ajaxPrefilter
aby zawinąć jqXHR w inny deferred object.
Zrobiłem przykład na jsFiddle
to pokazuje, że działa, i próbował dostosować część kodu do obsługi 401 do tej wersji:
$.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);
});
To działa, ponieważ deferred.promise(object)
faktycznie nadpisze wszystkie "metody obietnic" w jqxhr.
Uwaga: do każdego, kto to znajdzie, jeśli dołączasz wywołania zwrotne z success:
i error:
w opcjach ajax, ten fragment będzie , a nie działać tak, jak oczekujesz. Zakłada ona, że jedynymi wywołaniami zwrotnymi są te dołączone przy użyciu metod .done(callback)
i .fail(callback)
jqxhr.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-09 19:18:47
Jak zauważa odpowiedź gnarf, wywołania zwrotne sukcesu i błędów nie będą zachowywać się zgodnie z oczekiwaniami. Jeśli ktoś jest zainteresowany tutaj jest wersja, która obsługuje zarówno success
i error
wywołań zwrotnych, jak i obiecuje styl zdarzeń.
$.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);
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-11 13:25:40
Stworzyłem jQuery plugin do tego przypadku użycia. Owija logikę opisaną w odpowiedzi gnarfa w wtyczce i dodatkowo pozwala określić limit czasu oczekiwania przed ponowną próbą wywołania ajax. Na przykład.
//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!");
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-22 17:51:12
Czy coś takiego ci się uda? Musisz tylko zwrócić własną odroczoną / obiecaną obietnicę, aby Oryginalna nie została odrzucona zbyt szybko.
Przykład / użycie testu: 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();
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-13 03:24:05
To jest świetne pytanie, z którym też się spotkałem.
Zniechęciła mnie zaakceptowana odpowiedź (od @ gnarf), więc wymyśliłem sposób, który łatwiej zrozumiałem:
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));
}
Zasadniczo, właśnie owinąłem całe wywołanie Ajax i jego wywołania zwrotne w jedną funkcję, która może być wywoływana rekurencyjnie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-01 21:16:15