Статьи

Кеширование, JQuery Ajax и другие IE Fun

В последние пару дней мне было очень весело отлаживать проблемы наших JavaScript SPAs на oldIE (слава Богу, мы получили лицензии Browserstack). В любом случае, я столкнулся с довольно интересным поведением IE при вызовах jQuery Ajax и кэшированных ответах.

«HTTP / 1.1 304 не изменен»: ни за что! Ты меня не поймаешь

Чтобы этот эксперимент работал, убедитесь, что ваш кеш браузера активен.

Перенесите следующий ajax-запрос в статический файл JSON

$.ajax({
        url: '/data.json',
        type: 'GET',
        dataType: 'JSON',
        success: function(data, textStatus, jqXHR){
            console.log('Data received: ' + data);
        }
    });

Первый запрос, вероятно, возвращается через некоторое Http/1.1 200 OKвремя, а любой последующий (если на сервере активировано кэширование) возвращается с Http/1.1 304 Not Modified. Вы хорошо видите это на панели Chrome Network

Теперь предположим, что вы хотите выполнить какую-то операцию только Http/1.1 200над 304запросами, а не над ними . Достаточно просто, правда? Вы просто сверяете с jqXHR.statusобъектом:

...
success: function(data, textStatus, jqXHR){
    if(jqXHR.status === 200){
        console.log('Got a good request');
    }
}

Интересно, что для вышеупомянутого запроса я бы получил

Got a good request
Got a good request
Got a good request

… Напечатано на моей консоли и, действительно, при проверке по запросу jqXHR jqXHR.statusвозвращается как 200для, Http/1.1 200так и для Http/1.1 304. Почему это?? Это дизайн объекта XMLHttpRequest .

Для 304 неизмененных ответов, которые являются результатом условного запроса, сгенерированного пользовательским агентом, пользовательский агент должен действовать так, как если бы сервер дал ответ 200 OK с соответствующим содержимым. […] W3c.org [RFC2616]

Подожди .. есть способ!

На самом деле есть способ также получить 304коды состояния в вашем обратном вызове jQuery ajax. Очередной раз:

Пользовательский агент должен разрешить setRequestHeader () переопределять автоматическую проверку кэша, устанавливая заголовки запроса (например, If-None-Match, If-Modified-Since), в этом случае должны передаваться ответы 304 Not Modified. w3c.org [RFC2616]

Но обратите внимание, у вас нет доступа к информации браузера If-Modified-Sinceили E-Tag. Однако jQuery отслеживает это и позволяет использовать ifModifiedфлаг для параметров ajax:

$.ajax({
    url: '/data.json',
    type: 'GET',
    dataType: 'JSON',
    ifModified:true,
    success: function(data, textStatus, jqXHR){
        if(jqXHR.status === 200){
            console.log('Got a good request');
        } else {
            console.log('Hey, a 304 request');
        }

        console.log('Data: ' + data);
    }
});

Выполняя это сейчас снова, где первый запрос возвращает 200, а последующие 300 статус ответа дает мне следующее в журнале:

Got a good request
Data: <snip>JSON string</snip>
Hey, a 304 request
Data: undefined
Hey, a 304 request
Data: undefined

Черт возьми ?? Да, опять это по замыслу:

[…] В этом случае 304 Неизмененные ответы должны быть пропущены через […]

… и действительно, 304ответ не несет никаких данных.

Хорошо, но почему это должно быть проблемой?

Обычно это не так, просто не ставьте ifModifiedфлаг, и вы в порядке. Однако в моем конкретном сценарии мне пришлось анализировать изменения в определенном заголовке ответа, поэтому я использовал такую ajaxComplete(..)функцию, как

var currentHeaderVal = undefined;

$(document).ajaxComplete(function(e, xhr, settings){
    var newHeaderVal = xhr.getResponseHeader('MY-HEADER');
    if(currentHeaderVal !== newHeaderVal){
        //do something fancy
    }
});

На стороне сервера какой-то механизм ввел MY-HEADERзначение. Идея проста, и она также работает (на Chrome, Firefox и любом другом достойном браузере), но, к удивлению, на IE это не так. При использовании xhr.getResponseHeader('MY-HEADER')IE возвращает заголовки из ранее кэшированного запроса, которые, очевидно, уже не обязательно действительны.

Таким образом, идея состояла в том, чтобы проверить xhr.status, чтобы просто прочитать заголовки из 200запросов типа, как

var newHeaderVal = xhr.getResponseHeader('MY-HEADER');
if(xhr && xhr.status === 200){
    if(currentHeaderVal !== newHeaderVal){
        //do something fancy
    }
}

… но, как мы теперь знаем, это не сработает.