Как корректно отменить (прервать) AJAX-запрос в jQuery?
Решение: отмена AJAX-запроса через сохранение ссылки на XHR-объект
Да, вы всё верно подметили — для отмены запроса нужно сохранить ссылку на возвращаемый объект jqXHR (это обёртка jQuery над нативным XMLHttpRequest). При новом клике вызываем .abort() на предыдущем объекте. Вот как это правильно сделать:
// Переменная для хранения текущего запроса
var currentRequest = null;
$('.load-data').click(function() {
var id = $(this).data('id');
// Если есть незавершённый запрос — отменяем его
if (currentRequest) {
currentRequest.abort();
}
// Сохраняем новый запрос
currentRequest = $.ajax({
url: '/api/data/' + id,
method: 'GET',
success: function(response) {
$('#content').html(response);
currentRequest = null; // очищаем после успеха
},
error: function(xhr, status, error) {
// Не показываем ошибку, если это отмена
if (status !== 'abort') {
console.log('Ошибка: ' + error);
}
currentRequest = null; // очищаем в любом случае
}
});
});
Важные моменты
- Проверка статуса 'abort' — когда вы вызываете
.abort(), jQuery генерирует событие ошибки с текстом 'abort'. Чтобы не показывать пользователю ложное сообщение об ошибке, мы проверяем статус. - Очистка currentRequest — после завершения запроса (успех или ошибка) обязательно обнуляем переменную, чтобы не пытаться отменить уже завершённый запрос.
- Сервер всё равно получит запрос —
.abort()работает только на стороне клиента. Сервер может выполнить запрос, но браузер проигнорирует ответ. Если это критично (например, запрос меняет данные в БД), нужно реализовать токены отмены на сервере.
Более элегантный подход с замыканием
Чтобы не загрязнять глобальную область видимости, можно использовать замыкание через IIFE или привязать запрос к самому элементу:
$('.load-data').click(function() {
var $btn = $(this);
var id = $btn.data('id');
// Отменяем предыдущий запрос, сохранённый в data-атрибуте
var prevRequest = $btn.data('request');
if (prevRequest) {
prevRequest.abort();
}
var newRequest = $.ajax({
url: '/api/data/' + id,
success: function(response) {
$('#content').html(response);
$btn.removeData('request');
},
error: function(xhr, status) {
if (status !== 'abort') {
console.log('Ошибка');
}
$btn.removeData('request');
}
});
$btn.data('request', newRequest);
});
Современный подход через $.ajaxSetup (глобально)
Если нужно отменять все предыдущие запросы при любом новом AJAX-вызове, можно использовать:
var pendingRequests = {};
$.ajaxSetup({
beforeSend: function(jqXHR, settings) {
// Отменяем все запросы на тот же URL
if (pendingRequests[settings.url]) {
pendingRequests[settings.url].abort();
}
pendingRequests[settings.url] = jqXHR;
},
complete: function(jqXHR, settings) {
delete pendingRequests[settings.url];
}
});
Этот подход автоматически отменяет запросы к одному и тому же URL при повторных вызовах.
<<Альтернатива: использование флага и проверка актуальности запроса
Вместо принудительной отмены можно использовать простой флаг-счётчик. Это полезно, если вы не хотите вызывать abort() (например, чтобы не тратить ресурсы на отмену уже отправленного запроса). Идея: при каждом клике увеличиваем счётчик, а в success-колбэке проверяем, актуален ли ещё этот запрос.
var requestCounter = 0;
$('.load-data').click(function() {
var id = $(this).data('id');
var currentCounter = ++requestCounter;
$.ajax({
url: '/api/data/' + id,
success: function(response) {
// Обновляем контент только если это последний запрос
if (currentCounter === requestCounter) {
$('#content').html(response);
}
}
});
});
Плюсы: не нужно возиться с объектами запросов, код проще. Сервер всё равно обработает все запросы, но в интерфейс попадёт только результат последнего.
Минусы: если запросы тяжёлые и их много, это может создать нагрузку на сервер. Также возможны гонки данных, если запросы изменяют состояние на сервере.
Этот подход часто используют в поисковых подсказках (autocomplete), где важна скорость реакции интерфейса, а не экономия трафика сервера.