Можно ли отменить отсрочку jQuery?
У меня есть ситуация, когда я хочу отменить отложенную. Отложенная связь связана с вызовом ajax.
Почему я использую отложенные
Я не использую обычные объекты xhr, возвращаемые $.ajax. Я использую jsonp, а это значит, что я не могу использовать коды состояния HTTP для обработки ошибок и должен встраивать их в ответы. Затем коды проверяются, и соответствующий отложенный объект помечен как разрешенный или отклоненный соответствующим образом. У меня есть пользовательская функция api, которая делает это для меня.
function api(options) {
var url = settings('api') + options.url;
var deferred = $.Deferred(function(){
this.done(options.success);
this.fail(options.error);
});
$.ajax({
'url': url,
'dataType':'jsonp',
'data': (options.noAuth == true) ? options.data : $.extend(true, getAPICredentials(), options.data)
}).success(function(jsonReturn){
// Success
if(hasStatus(jsonReturn, 'code', 200)) {
deferred.resolveWith(this, [jsonReturn]);
}
// Failure
else {
deferred.rejectWith(this, [jsonReturn]);
}
});
return deferred;
}
Почему я хочу отменить отложенную
Существует поле ввода, которое служит фильтром для списка и автоматически обновляет список через полсекунды после ввода концов. Поскольку два вызова ajax могут быть выдающимися одновременно, мне нужно отменить предыдущий вызов, чтобы он не возвращался после второго и отображал старые данные.
Решения, которые мне не нравятся
- Я не хочу отклонять отложенное, потому что это приведет к срабатыванию обработчиков с помощью
.fail()
.
- Я не могу игнорировать его, потому что он автоматически будет отмечен как разрешенный или отклоненный при возврате ajax.
- Удаление отложенного приведет к ошибке при возврате вызова ajax и пытается пометить отложенное как разрешенное или отклоненное.
Что мне делать?
Есть ли способ отменить отложенные или удалить все прикрепленные обработчики?
Совет о том, как исправить мой дизайн, приветствуется, но предпочтение будет отдаваться поиску способа удаления обработчиков или предотвращения их стрельбы.
Ответы
Ответ 1
Глядя в документ и код jQuery, я не вижу способа отменить jQuery отложенным.
Вместо этого вам, вероятно, нужен способ в вашем обработчике resolveWith
, чтобы узнать, что последующий вызов ajax уже запущен, и этот вызов ajax должен игнорировать его результат. Вы можете сделать это с помощью счетчика с глобальным увеличением. В начале вашего вызова ajax вы увеличиваете счетчик, а затем получаете значение в локальной переменной или помещаете его как свойство в объект ajax. В вашем обработчике resolveWith
вы проверяете, имеет ли счетчик все те же значения, что и при запуске вашего вызова ajax. Если нет, вы проигнорируете результат. Если это так, новых вызовов ajax не было, поэтому вы можете обработать результат.
В качестве альтернативы вы можете отказаться от запуска нового вызова ajax, когда вы уже в полете, поэтому у вас никогда не было более одного в полете за раз. Когда кто-то закончит, вы можете либо использовать этот результат, либо пристрелить следующий, если хотите.
Ответ 2
Пока вы не можете "отменить" отложенную, как вы хотите, вы можете создать простое закрытие, чтобы отслеживать последний вызов ajax через $.ajax, возвращающий объект jqXHR. Поступая таким образом, вы можете просто прекратить() вызов, когда новый jqXHR входит в игру, если последний не был завершен. В вашем случае кода он отклонит jqXHR и оставьте отложенное открытое для удаления, как вы хотели.
var api = (function() {
var jqXHR = null;
return function(options) {
var url = options.url;
if (jqXHR && jqXHR.state() === 'pending') {
//Calls any error / fail callbacks of jqXHR
jqXHR.abort();
}
var deferred = $.Deferred(function() {
this.done(options.success);
this.fail(options.error);
});
jqXHR = $.ajax({
url: url,
data: options.toSend,
dataType: 'jsonp'
});
jqXHR.done(function(data, textStatus, jqXHR) {
if (data.f && data.f !== "false") {
deferred.resolve();
} else {
deferred.reject();
}
});
//http://api.jquery.com/deferred.promise/
//keeps deferred state from being changed outside this scope
return deferred.promise();
};
})();
Я разместил это на jsfiddle. Если вы хотите проверить это. Установка таймаута используется в сочетании с задержкой jsfiddles для имитации переадресации вызова. Для просмотра журналов вам понадобится браузер с поддержкой консоли.
На стороне примечания переключите любые методы .success(),.error() и complete() на отложенные методы done(), fail() и always(). Через jquery/ajax
Уведомление об изъятии: вызовы jqXHR.success(), jqXHR.error() и jqXHR.complete() будут устаревать в jQuery 1.8. Чтобы подготовить код для их возможного удаления, используйте jqXHR.done(), jqXHR.fail() и jqXHR.always() вместо более новых
Ответ 3
JustinY: похоже, вы уже близко к тому, что хотите. Вы уже используете две отложенные (внутренние → ajax и внешние → $.Deferred()). Затем вы используете внутреннее отложенное решение о том, как разрешить внешнюю отсрочку, основанную на некоторых условиях.
Ну, так что просто не разрешайте внешние отложенные вообще, когда вы этого не хотите (возможно, у вас есть логическая переменная, которая служит в качестве переключателя для разрешения внутреннего dfd для разрешения/отклонения вообще). Ничего плохого не произойдет: все обработчики, которые вы привязали ко всей этой функции, не будут срабатывать. Пример в вашей внутренней функции успеха:
if(gateOpen){
gateOpen = false;
if(hasStatus(jsonReturn, 'code', 200)) {
deferred.resolveWith(this, [jsonReturn]);
}
else {
deferred.rejectWith(this, [jsonReturn]);
}
}
Некоторая другая логика в приложении решит, когда gateOpen вернется к истинному (некоторый тайм-аут _.throttle() или _.debounce(), взаимодействие с пользователем, что бы вы ни захотели). Если вы хотите отслеживать или отмените другие запросы в другом месте этой функции, вы тоже можете это сделать. Но основная вещь заключается в том, что вам не нужно разрешать ИЛИ отклонять внешний внешний вид. И это то же самое, что отменить его, даже если вы не отмените/не прервите внутренний.
Ответ 4
Я создал прокладку, которая легко добавляет возможность отменить отложенные объекты и запросы ajax.
Короче говоря, как только отложенный объект был отменен, разрешения/отклонения полностью игнорируются, а state
становится "отмененным".
Согласно jQuery.com, "Как только объект вошел в разрешенное или отклоненное состояние, он остается в этом состоянии". Поэтому попытки отмены игнорируются, как только отложенный объект будет разрешен или отклонен.
(function () {
originals = {
deferred: $.Deferred,
ajax: $.ajax
};
$.Deferred = function () {
var dfr = originals.deferred(),
cancel_dfr = originals.deferred();
dfr.canceled = false;
return {
cancel: function () {
if (dfr.state() == 'pending') {
dfr.canceled = true;
cancel_dfr.resolve.apply(this, arguments);
}
return this;
},
canceled: cancel_dfr.done,
resolve: function () {
if ( ! dfr.canceled) {
dfr.resolve.apply(dfr, arguments);
return this;
}
},
resolveWith: function () {
if ( ! dfr.canceled) {
dfr.resolveWith.apply(dfr, arguments);
return this;
}
},
reject: function () {
if ( ! dfr.canceled) {
dfr.reject.apply(dfr, arguments);
return this;
}
},
rejectWith: function () {
if ( ! dfr.canceled) {
dfr.rejectWith.apply(dfr, arguments);
return this;
}
},
notify: function () {
if ( ! dfr.canceled) {
dfr.notify.apply(dfr, arguments);
return this;
}
},
notifyWith: function () {
if ( ! dfr.canceled) {
dfr.notifyWith.apply(dfr, arguments);
return this;
}
},
state: function () {
if (dfr.canceled) {
return "canceled";
} else {
return dfr.state();
}
},
always : dfr.always,
then : dfr.then,
promise : dfr.promise,
pipe : dfr.pipe,
done : dfr.done,
fail : dfr.fail,
progress : dfr.progress
};
};
$.ajax = function () {
var dfr = $.Deferred(),
ajax_call = originals.ajax.apply(this, arguments)
.done(dfr.resolve)
.fail(dfr.reject),
newAjax = {},
ajax_keys = [
"getResponseHeader",
"getAllResponseHeaders",
"setRequestHeader",
"overrideMimeType",
"statusCode",
"abort"
],
dfr_keys = [
"always",
"pipe",
"progress",
"then",
"cancel",
"state",
"fail",
"promise",
"done",
"canceled"
];
_.forEach(ajax_keys, function (key) {
newAjax[key] = ajax_call[key];
});
_.forEach(dfr_keys, function (key) {
newAjax[key] = dfr[key];
});
newAjax.success = dfr.done;
newAjax.error = dfr.fail;
newAjax.complete = dfr.always;
Object.defineProperty(newAjax, 'readyState', {
enumerable: true,
get: function () {
return ajax_call.readyState;
},
set: function (val) {
ajax_call.readyState = val;
}
});
Object.defineProperty(newAjax, 'status', {
enumerable: true,
get: function () {
return ajax_call.status;
},
set: function (val) {
ajax_call.status = val;
}
});
Object.defineProperty(newAjax, 'statusText', {
enumerable: true,
get: function () {
return ajax_call.statusText;
},
set: function (val) {
ajax_call.statusText = val;
}
});
// canceling an ajax request should also abort the call
newAjax.canceled(ajax_call.abort);
return newAjax;
};
});
После добавления вы можете отменить вызов ajax:
var a = $.ajax({
url: '//example.com/service/'
});
a.cancel('the request was canceled');
// Now, any resolutions or rejections are ignored, and the network request is dropped.
.. или простой отложенный объект:
var dfr = $.Deferred();
dfr
.done(function () {
console.log('Done!');
})
.fail(function () {
console.log('Nope!');
});
dfr.cancel(); // Now, the lines below are ignored. No console logs will appear.
dfr.resolve();
dfr.reject();