Недостатком объекта XMLHttpRequest является отсутствие возможности ограни_ чить время ожидания исполнения запроса. Этот недостаток особенно критичен для синхронных запросов. Если связь с сервером пропадет, веб_броузер окажет_ ся заблокированным в методе send() и не будет реагировать на действия пользо_ вателя. В случае асинхронных запросов такого не происходит, поскольку метод send() не блокируется, и веб_броузер может продолжать реагировать на действия пользователя. Однако и здесь существует проблема ограничения времени выпол_ нения запроса. Предположим, что приложение с помощью объекта XMLHttpRe_ quest запустило HTTP_запрос, когда пользователь щелкнул на кнопке. Чтобы предотвратить возможность отправки нескольких запросов, нелишне сделать кнопку неактивной до того момента, как прибудет ответ сервера. Но что если сервер остановится или произойдет нечто, что помешает получению ответа на за_ прос? Броузер не будет заблокирован, но возможности приложения из_за неак_ тивной кнопки окажутся ограниченными.
Чтобы избежать проблем подобного рода, было бы удобно иметь возможность ус_ танавливать собственные тайм_ауты с помощью функции Windows.setTimeout() при выполнении HTTP_запросов. В обычной ситуации ответ приходит до того, как будет вызван обработчик события таймера, – в этом случае можно просто вызвать функцию Window.clearTimeout(), чтобы отменить срабатывание таймера. С другой стороны, если обработчик события от таймера будет вызван раньше,
508 Глава 20. Работа с протоколом HTTP
чем свойство readyState получит значение 4, исполнение запроса можно будет от_ менить с помощью метода XMLHttpRequest.abort(). После этого обычно следует из_ вестить пользователя о том, что попытка выполнения запроса потерпела неуда_ чу (например, методом Window.alert()). Если в этом гипотетическом примере пе_ ред запуском запроса кнопка была деактивирована, ее можно будет повторно ак_ тивировать по истечении предельного времени ожидания.
В примере 20.7 определяется функция HTTP.get(), которая демонстрирует только что описанный прием организации тайм_аута. Она представляет собой усовер_ шенствованную версию функции HTTP.getText() из примера 20.2 и поддерживает многие из возможностей, демонстрировавшихся в предыдущих примерах, вклю_ чая обработку ошибок, параметры запроса и метод HTTP._getResponse(), описан_ ный выше. Кроме того, она допускает возможность указать необязательную функцию обратного вызова, которая будет вызываться всякий раз, когда про_ изойдет событие onreadystatechange со значением свойства readyState, отличным от 4. В таких броузерах, как Firefox, обработчик этого события может вызывать_ ся неоднократно со значением 3, и данная функция обратного вызова позволяет сценарию демонстрировать пользователю индикатор процесса загрузки.
Пример 20.7. Вспомогательная функция HTTP.get()
/**
* Отправляет HTTP_запрос GET с заданным URL. В случае успешного
* получения ответа он преобразуется в объект на основе заголовка
* Content_Type и передается указанной функции обратного вызова.
* Дополнительные аргументы могут быть переданы в виде свойств объекта options.
*
* Если получен ответ с сообщением об ошибке (например, сообщение
* 404 Not Found), код состояния и сообщение передаются функции
* options.errorHandler. Если обработчик ошибок не определен, вызывается
* функция обратного вызова со значением null в аргументе.
*
* Если объект options.parameters определен, его свойства интерпретируются
* как имена и значения параметров запроса. С помощью HTTP.encodeFormData()
* они преобразуются в строку, которую можно вставить в URL, после чего эта
* строка добавляется в конец URL вслед за символом '?'.
*
* Если определена функция options.progressHandler, она будет вызываться
* всякий раз, когда свойство readyState обретает новое значение, меньшее 4.
* Каждый раз этой функции будет передаваться количество вызовов этой функции.
*
* Если указано значение options.timeout, работа объекта XMLHttpRequest будет
* прервана, если запрос не будет исполнен до истечения заданного числа миллисекунд.
* Если предельное время ожидания истекло и определена функция
* options.timeoutHandler, она будет вызвана со строкой
* URL запроса в виде аргумента.
*
**/
HTTP.get = function(url, callback, options) { var request = HTTP.newRequest();
var n = 0; var timer;
if (options.timeout)
20.3. Ajax и динамические сценарии
timer = setTimeout(function() { request.abort();
if (options.timeoutHandler) options.timeoutHandler(url);
},
options.timeout);
request.onreadystatechange = function() { if (request.readyState == 4) {
if (timer) clearTimeout(timer); if (request.status == 200) {
callback(HTTP._getResponse(request));
}
else {
if (options.errorHandler) options.errorHandler(request.status,
request.statusText);
else callback(null);
}
}
else if (options.progressHandler) { options.progressHandler(++n);