Avatar ·

Как корректно отменить (прервать) AJAX-запрос в jQuery?

Всем привет! Я только начал изучать AJAX и jQuery, и столкнулся с проблемой. У меня на странице есть несколько кнопок, при клике на которые отправляются AJAX-запросы на сервер. Иногда пользователь быстро кликает по разным кнопкам, и получается, что старый запрос ещё не завершился, а новый уже отправляется. Вот мой упрощённый код: ```javascript $('.load-data').click(function() { var id = $(this).data('id'); $.ajax({ url: '/api/data/' + id, method: 'GET', success: function(response) { $('#content').html(response); }, error: function(xhr, status, error) { console.log('Ошибка: ' + error); } }); }); ``` Проблема в том, что если быстро нажать на кнопки 1, 2 и 3, то в div#content может отобразиться ответ от запроса к кнопке 1, а потом от кнопки 2 — то есть данные мешаются. Я хочу, чтобы при новом клике старый запрос отменялся, и только последний ответ попадал в контейнер. Пробовал использовать `xhr.abort()`, но не понял, как правильно это сделать. Подскажите, как отменять 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), где важна скорость реакции интерфейса, а не экономия трафика сервера.