Наиболее эффективное использование асинхронных обратных вызовов

Основной проблемой при работе с асинхронными источниками данных является то, что… они не синхронны. В частности, данные, пересылаемые по протоколу HTTP, могут поступить значительно позже, чем предполагалось, время обработки запросов может превысить тайм-аут или же они могут просто завершиться неудачно. Ненадежность является аспектом работы через любой протокол уровня TCP, но приложения Ajax могут быть так же зависимы по данным от нескольких серверов, которые необходимы для работы единого Web-приложения.

Проблема разрешения зависимостей по данным встречается не только в приложениях Ajax. Множество различных приложений используют семафоры, очереди, совместно используемые переменные и другие способы для организации взаимодействия между процессами. В нашем случае процессом будет, как правило, являться запрос на получение данных. Однако, относительная вероятность тайм-аута и прочих проблем, связанных с сервером или сетью, значительно выше при работе Web-приложений, чем большинства других программ, в особенности тех, которые выполняются исключительно локально. Более того, разброс по времени обращения к разным источникам данных (или даже при множественных обращениях к одному источнику) значительно шире для Web-приложений, чем для большинства других, например, баз данных, которые так же обращаются к сетевым ресурсам.


Состояние запроса и тайм-ауты

В большинстве примеров, демонстрирующих асинхронные вызовы Ajax с помощью объекта XMLHttpRequest(), проверяется только получение статуса 200 OK. Я бы рекомендовал включить дополнительные проверки на наличие нескольких других признаков, в том числе проверять “псевдо-состояние” в случае немедленного тайм-аута (outright timeout). В своей недавней статье-совете “Как избежать необязательного Ajax-трафика, используя состояние сессии”, опубликованной на сайте developerWorks (ссылку можно найти в разделе Ресурсы), я привел пример использования кода состояния 304. Его можно успешно применять и во многих других случаях.

Хорошей идеей является корректная обработка множества признаков состояния, использующихся в протоколе HTTP. При этом остается открытым вопрос, что делать при получении признаков 2xx отличных от 200 OK. В частности, если ваше приложение создает ресурс на сервере, то имеет смысл проверить код 201 Created и обратить внимание на URI в ответе. Согласно описанию протокола при получении ответов с признаками 301, 302, 303 или 303 клиенты должны просто перенаправлять запрос по полученному адресу. Поэтому эти признаки можно не различать, так как в конечном итоге все равно придет ответ со статусом 200 (кроме этого возможен тайм-аут или один из признаков 4xx или 5xx). Имеет смысл различать получение ответов с признаками 4xx от 5xx. В общем случае признаки 4xx (особенно самый распространенный из них – 404 Not Found), как правило, означают, что клиентское приложение использует неправильный URL. Ошибки 5xx скорее всего указывают на проблемы сервера, поэтому в этих случаях имеет смысл немного подождать и повторно обратиться по тому же URL.

Кроме обработки различных серверных ошибок так же необходимо быть готовым к ситуации, когда сервер “зависает”, не возвращая вообще никакого ответа. Подобные ситуации полностью эквивалентны признакам 5xx за тем исключением, что нельзя просто вызывать метод XMLHttpRequest().status для идентифицирования статуса. В качестве альтернативного способа можно устанавливать таймер с помощью вызова setTimeout() для отмены запроса по истечении тайм-аута.

В листинге 1 показан пример кода, выполняющего запрос на получение данных через Ajax. В примере выполняются все вышеописанные проверки:


Листинг 1. Полноценная обработка результатов запросов на получение данных

function getResource(uri, data_callback, error_callback, timeout) {
    var tryAgain = function () {
      getResource(uri, data_callback, error_callback, timeout);
    }
    var r = new XMLHttpRequest();
    var timer = setTimeout(
        function() {
            r.abort();
            r.onreadystatechange = null;
            setTimeout(tryAgain, timeout);
        },
        timeout);
    r.open("GET", uri, true);
    r.onreadystatechange = function() {
        if (r.readyState != 4) {
            // Игнорируем состояния, которые не указывают на получение данных
            // ... если данные не будут получены, 
            // то выполнение будет прервано по тайм-ауту
            return;
        }
        clearTimeout(timer);  // readyState==4, выключаем таймер
        if (r.status==200) {  // "признак состояния OK"
              data_callback(r.responseText);
        }
        else if (r.status==304) {
            // "Not Modified": Нет изменений для показа пользователю
        }
        else if (r.status >= 400 && r.status < 500) {
            // Клиентская ошибка, вероятно, неправильный URI
            error_callback(r)
        }
        else if (r.status >= 500 && r.status < 600) {
            // Серверная ошибка, повторим запрос после паузы
            setTimeout(tryAgain, timeout);
        }
        else {
            error_callback(r);
        }
    }
    r.send(null);
    return r;
}

 

Разумеется, существует множество способов улучшения функции getResource(). Например, в случае тайм-аута или получения признака, указывающего на серверную ошибку, в примере просто вызывается функция tryAgain() с некоторой задержкой. Вместо этого в каждом случае можно использовать собственную функцию обратного вызова. Кроме того, в ситуации, когда повторный запрос, скорее всего, также окажется неудачным, мы, возможно, поступаем слишком пессимистично и сразу вызываем переданную извне функцию error_callback().


Координирование данных

Возможна ситуация, когда приведенная выше функция может ждать получения данных бесконечно долго перед тем, как передать их в функцию data_callback(). При этом выполнение функции обратного вызова data_callback() может так же зависеть от доступности других данных. Манипулировать несколькими источниками данных проще всего, помещая их в глобальные переменные, чьи значения можно очищать по мере использования данных. В листинге 2 приведен один из вариантов реализации простой функции обратного вызова:


Листинг 2. Пример типичной функции обратного вызова для получения данных из двух источников

var other_data = null;
function processOtherData(responseText) {
    other_data = responseText;
}
function processData(this_data) {
    var delay = 1000;     // Повторять запросы через секундные интервалы
    if (other_data == null) {
        setTimeout(function() { processData(this_data); }, delay);
        return;
    }
    // Данные получены из обоих источников 
    displayThisAndThat(this_data, other_data);
    // Очистить значение переменной other_data, т.к. данные были обработаны
    other_data = null;
}

 

Приложение будет сначала вызывать getResource(uri1,processOtherData,...), а затем в какой-то другой точке - getResource(uri2,processData,...). Во время второго выполнения метода будет запущена функция обратного вызова, которая будет опрашивать переменную other_data до тех пор, пока в ней не появится сохраненное значение. После обработки данных функция вновь обнулит значение переменной. При этом имеет смысл использовать еще один семафор, чтобы избежать множественных вызовов второй функции до того момента, как успешно выполнится первая, а так же выполнять clearTimeout() для инициирования нового вызова processData() взамен отложенного.


Заключение

Сценарии координации данных могут быть сколь угодно сложными в любом приложении, не только приложении Ajax. В приведенном выше коде используется только одна функция, обрабатывающая данные – processData(), которая использует вспомогательную функцию processOtherData() для получения дополнительной информации. Несмотря на внешнюю простоту примера, он представляет собой распространенный вариант работы с взаимозависимыми источниками данных.

Центр материалов по Ajax на сайте developerWorks
Обратитесь к центру материалов по Ajax– единому собранию бесплатных утилит, кода и информации о разработке приложений Ajax. На активно действующем форуме сообщества Ajax, модерируемом экспертом Джеком Херрингтоном (Jack Herrington), вы сможете общаться с коллегами, возможно, знающими решение проблемы, над которой вы ломаете голову в данный момент.

Так или иначе, функция getResource() является неплохой иллюстрацией “настойчивых” попыток получения данных по протоколу HTTP. В конце концов, она их получит, в случае, разумеется, отсутствия клиентских ошибок при формировании запросов. Другими словами, временные трудности, испытываемые сетью или сервером, означают только задержки при получении данных, а не окончательный отказ. Конечно, как и всегда в приложениях Ajax, вам придется приложить специальные усилия для получения данных из различных источников. Как правило, это достигается путем использования объектов XMLHttpRequest() в отдельных скрытых фреймах (IFrame), но в этом нет никаких особенностей, которые были бы характерны только для асинхронной координации.




Рекомендуем почитать

 

Добавить комментарий


Ваше имя:


Комментарий:


Введите: Картинка