Прокачиваем AJAX

Статья не для начинающих ☝️🧐

У нас есть три способа для «прокачки» AJAX в jQuery: это создание префильтров, добавление новых конверторов и транспортов.

Префильтры

Префильтр – это функция, которая будет вызвана до события ajaxStart, в ней вы сможете изменить как объект jqXHR, так и любые сопутствующие настройки:

// регистрация AJAX префильтра
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
    // наши манипуляции над настройками и jqXHR
});

Для чего всё это? Да вот простая задачка – не ждать «старый» AJAX-ответ, если мы запрашиваем URL заново:

// коллекция текущих запросов
var currentRequests = {};
$.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
    // наша произвольная настройка
    if ( options.abortOnRetry ) {
        if ( currentRequests[ options.url ] ) {
            // отменяем старый запрос
            currentRequests[ options.url ].abort();
        }
        currentRequests[ options.url ] = jqXHR;
    }
});

// вызов с использованием фильтра
$.ajax({
    /* ... */
    abortOnRetry: true
})

Ещё можно изменить опции вызова, вот пример, который по флагу crossDomain пересылает запрос на заранее подготовленную проксирующую страницу на нашем сервере:

$.ajaxPrefilter(function( options ) {
    if ( options.crossDomain ) {
        options.url = "/proxy/" + encodeURIComponent( options.url );
        options.crossDomain = false;
    }
});

Префильтры можно «вешать» на определенный тип dataType (т.е. в зависимости от ожидаемого типа данных от сервера будут срабатывать различные фильтры):

$.ajaxPrefilter("json script", function(options, original, jqXHR) {
    /* ... */
});

Ну и последнее: для переключения dataType на какой-нибудь другой тип нам достаточно будет вернуть необходимое значение:

$.ajaxPrefilter(function( options ) {
    // это наша функция-детектор необходимых URL
    if ( isActuallyScript( options.url ) ) {
        // теперь «ждём» script
        return "script";
    }
});

Будьте очень осторожны, когда оперируете глобальными настройками, да ещё через такую неявную фичу, как фильтры. Задокументируйте подобные подходы в сопроводительной документации, иначе разработчики, которые будут в дальнейшем сопровождать ваш код, будут сильно ругаться (в качестве оных через пару месяцев можете оказаться и вы сами). И в целом настоятельно рекомендуется достаточно подробное документирование и комментирование кода. Меткая фраза, ошибочно приписываемая мэтру программирования МакКоннеллу: "Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте" (Джон Ф. Вудс).

Конверторы

Конвертор – функция обратного вызова, которая вызывается в том случае, когда полученный тип данных не совпадает с ожидаемым (т.е. dataType указан неверно).

Все конверторы хранятся в глобальных настройках ajaxSettings:

// формат ключа "из_формата в_формат"
// в качестве входного формата можно использовать "*"
{
    converters: {
        "* text":    String,         // что угодно приводим к тексту
        "text html": true,           // текст к HTML (флаг true == без изменений)
        "text json": JSON.parse,     // текст к JSON
        "text xml":  jQuery.parseXML // разбираем текст как XML
    }
}

Для расширения набора конверторов потребуется метод $.ajaxSetup():

$.ajaxSetup({
    converters: {
        "text mydatatype": function( textValue ) {
            if ( valid( textValue ) ) {
                // разбор пришедших данных
                return mydatatypeValue;
            } else {
                // возникла ошибка
                throw exceptionObject;
            }
        }
    }
});

Имена dataType должны всегда быть в нижнем регистре.

Конверторы следует использовать, если требуется внедрить произвольные форматы dataType, или для конвертации данных в нужный формат. Необходимый dataType указываем при вызове метода $.ajax():

$.ajax( url, { dataType: "mydatatype" });

Конверторы можно задавать также непосредственно при вызове $.ajax(), дабы не засорять общие настройки:

$.ajax( url, {
    dataType: "xml text mydatatype",
    converters: {
        "xml text": function( xmlValue ) {
            // получаем необходимые данные из XML
            return textValue;
        }
    }
});

Чуть-чуть пояснений: мы запрашиваем «XML», который конвертируем в текст, который будет передан в наш конвертор из «text» в «mydatatype».

Транспорт

Использование своего транспорта – это крайняя мера, прибегайте к ней только в том случае, если с поставленной задачей нельзя справиться с использованием префильтров и конверторов.

Транспорт – это объект, который предоставляет два метода send() и abort() – они будут использоваться внутри метода $.ajax(). Для регистрации своего метода транспортировки следует использовать метод $.ajaxTransport(), будет это выглядеть как-то так:

$.ajaxTransport( function( options, originalOptions, jqXHR ) {
    if ( /* transportCanHandleRequest */ ) {
        return {
            send: function( headers, completeCallback ) {
                /* отправляем запрос */
            },
            abort: function() {
                /* отменяем запрос */
            }
        };
    }
});

Поясню чуток параметры, с которыми будем работать:

options

настройки запроса т.е. то, что указываем при вызове $.ajax()

originalOptions

«чистые» настройки,

даже без учёта изменений «по умолчанию»

jqXHR

объект jQuery XMLHttpRequest

headers

заголовки запроса в виде связки «ключ-значение»

completeCallback

функция обратного вызова, её следует использовать для оповещения о завершении запроса

Функция completeCallback() имеет следующую сигнатуру:

function ( status, statusText, responses, headers ) {
    /* какой-то код */
}

где:

параметрописание

status

HTTP статус ответа

statusText

текстовая интерпретация статуса ответа

responses

опционально – объект, содержащий ответы сервера во всех форматах, которые поддерживает транспорт; например, родной XMLHttpRequest будет выглядеть как { xml: XMLData, text: textData } при запросе XML документа

headers

опционально – строка, содержащая заголовки ответа сервера, если транспорт может их получить; например, метод XMLHttpRequest.getAllResponseHeaders() это осилит

Как и префильтры, транспорт можно привязывать к определенному типу запрашиваемых данных:

$.ajaxTransport( "script", function( options, originalOptions, jqXHR ) {
    /* привязываемся лишь к script*/
});

А теперь мега-напряг – добавим транспорт «image» на нашу страницу:

// регистрируем его для типа image
$.ajaxTransport("image", function (options) {
    // отслеживаем лишь GET AJAX-запросы
    if (options.type === "GET" && options.async) {
        var image;
        return {
            // 
            send:function (_, callback) {
                image = new Image();
                // подготовим функцию done
                function done(status) {
                    if (image) {
                        var statusText = ( status == 200 ) ? "success" : "error", tmp = image;
                        image = image.onreadystatechange = image.onerror = image.onload = null;
                        callback(status, statusText, { image:tmp });
                    }
                }
                image.onreadystatechange = image.onload = function () {
                    done(200);
                };
                image.onerror = function () {
                    done(404);
                };
                image.src = options.url;
            },
            abort:function () {
                if (image) {
                    image = image.onreadystatechange = image.onerror = image.onload = null;
                }
            }
        };
    }
});

Теперь можно воспользоваться данным транспортом:

$.ajax('../code/img/photo-cat.jpg', {
    dataType:'image',
    success: function(data) {
        $('#image').html(data);
    }
})

Я хотел бы ещё раз напомнить, что это «advanced level», и данный раздел лишний в учебнике «для начинающих».

По следам официальной документации:

Last updated