Освоение Ajax: Часть 2. Выполнение асинхронных запросов с JavaScript и Ajax

Использование XMLHttpRequest для Web-запросов

Большинство Web-приложений используют модель запрос/ответ, в которой получают от сервера HTML-страницу полностью. В результате получаем работу по типу "вперед и назад", обычно состоящую из нажатия кнопки, ожидания сервера, нажатия другой кнопки и опять ожидания сервера. При помощи Ajax и объекта XMLHttpRequest вы можете использовать модель запрос/ответ, которая никогда не оставляет пользователей в ожидании ответа сервера. В данной статье Брэт Маклафлин расскажет о создании экземпляров XMLHttpRequest кросс-браузерным способом, построении и передаче запросов и реакции сервера.

Брэт Маклафлин, автор и редактор, O'Reilly Media Inc.

Photo of Brett McLaughlinБрэт Маклафлин (Brett McLaughlin) работает с компьютерами со времен Logo (помните маленький треугольник?). За последние несколько лет он стал одним из наиболее известных авторов и программистов сообщества по технологиям Java и XML. Он работал в Nextel Communications над реализацией сложных корпоративных систем, в Lutris Technologies, фактически, над созданием сервера приложений, а с недавних пор работает в O'Reilly Media, Inc., где продолжает писать и редактировать книги по данной тематике. В готовящейся Брэтом вместе с популярными авторами Эриком и Бет Фриманами книге Быстрый штурм Ajax использован общепризнанный и передовой подход к Ajax по методу Head First. Его недавняя книга "Java 1.5 Tiger: Заметки разработчика", является первой доступной книгой по новейшей технологии Java, а его классическая "Java и XML" остается одной из наиболее авторитетных работ по использованию технологий XML в языке программирования Java.



17.01.2006

В первой статье этой серии (ссылка приведена в разделе Ресурсы) вы познакомились с Ajax-приложениями и некоторыми основными концепциями, присущими Ajax-приложениям. В центре всего присутствует много технологий, о которых вы, вероятно, уже знаете: JavaScript, HTML, XHTML, немного динамического HTML и даже DOM (Document Object Model). В данной статье я спущусь с высоты 10000 футов и остановлюсь на конкретных деталях Ajax.

Вы начнете с самого фундаментального и базового из всех Ajax-объектов и программных подходов - объекта XMLHttpRequest. Он, фактически, является единственной общей нитью для всех Ajax-приложений, и (как можно ожидать) вы захотите тщательно изучить его для повышения вашей квалификации программиста до максимально возможных пределов. На самом деле когда-нибудь обнаружится, что для правильного использования XMLHttpRequest вы явно не хотите использовать XMLHttpRequest. Так зачем же все это, черт возьми?

Web 2.0 с первого взгляда

Прежде всего, я сделаю последний маленький обзор перед погружением в исходный код, чтобы убедиться, что вы совершенно ясно представляете эту идею с Web 2.0. Когда вы слышите термин Web 2.0, то должны прежде всего спросить: "А что такое Web 1.0?" Хотя вы вряд ли слышали про Web 1.0, этот термин означает ссылку на традиционный Web, в котором используется совершенно четкая модель запроса и ответа. Например, откройте сайт Amazon.com и нажмите кнопку или введите строку поиска. Этот запрос содержит значительно больше, чем просто список книг; это фактически другая полная HTML-страница. В результате вы, вероятно, увидите некоторое мигание и мерцание во время перерисовки Web-браузером этой новой HTML-страницы. На самом деле, можно ясно увидеть запрос и ответ, разграниченные каждой новой страницей, которая будет отображена.

Web 2.0 обходится без этой очень заметной процедуры "вперед и назад" (в значительной степени). В качестве примера посетите сайт Google Maps или Flickr (ссылки на оба этих Web 2.0 сайта, работающих на Ajax, приведены в разделе Ресурсы). На Google Maps, например, вы можете повернуть карту, приблизить или удалить ее с очень небольшой перерисовкой. Естественно, запросы и ответы выполняются и здесь, но это происходит в фоновом режиме. С точки зрения пользователя, впечатление очень хорошее; есть такое чувство, что работаешь с настольным приложением. Это новое чувство (и парадигма) и имеются в виду, когда идет речь о Web 2.0.

Единственное, о чем вы должны позаботиться, – как сделать возможными эти новые взаимодействия. Очевидно, вы все равно должны выполнять запросы и получать ответы, но эта перерисовка HTML для каждой операции запрос/ответ и дает ощущение медленного, тяжеловесного Web-интерфейса. Поэтому понятно, что необходим новый подход, позволяющий выполнять запросы и получать ответы, которые содержат только необходимые данные вместо целой HTML-страницы. Единственный момент, когда вы хотите получить полностью новую HTML-страницу, - это… ммм... когда вы хотите, чтобы пользователь увидел новую страницу.

Но большинство взаимодействий добавляют детали: меняют текстовое поле или перекрывают данные на существующих страницах. Во всех этих случаях подход Ajax и Web 2.0 делают возможным передачу и прием данных без обновления HTML-страницы полностью. И для каждого Web-серфера эта способность вашего приложения даст ощущение быстроты, большей чувствительности и будет приводить его к вам снова и снова.


Знакомство с XMLHttpRequest

Для того чтобы весь этот блеск и чудо произошли на самом деле, вы должны тесно познакомиться с объектом JavaScript, называемым XMLHttpRequest. Этот маленький объект – ключ к Web 2.0, Ajax и, в большой степени, ко всему прочему, что вы узнаете в этом разделе в течение последующих нескольких месяцев. Чтобы сделать действительно быстрый обзор, остановимся всего лишь на нескольких методах и свойствах, которые вы будете использовать в этом объекте:

  • open(): Устанавливает новый запрос к серверу;
  • send(): Передает запрос серверу;
  • abort(): Прекращает текущий запрос;
  • readyState: Хранит текущее состояние готовности HTML;
  • responseText: Текст, который сервер передал назад как ответ на запрос.

Не беспокойтесь, если вы не все понимаете из этого (или вообще ничего по этой теме) – вы изучите каждый метод и свойство в следующих нескольких статьях. Что вы должны вынести из этого – четкое представление о том, что делать с XMLHttpRequest. Обратите внимание, что каждый из этих методов и свойств относится к передаче запроса и работает с ответом. На самом деле, если вы видели все методы и свойства XMLHttpRequest, все они будут иметь отношение к этой очень простой модели запрос/ответ. Из этого понятно, что вы не будете изучать новый изумительный GUI-объект или определенного рода сверхсекретный подход к созданию взаимодействия с пользователем; вы будете работать с простыми запросами и простыми ответами. Это, возможно, звучит не очень захватывающе, но осторожное использование этого единственного объекта может полностью изменить ваши приложения.

Простота нового

Во-первых, вы должны создать новую переменную и присвоить ей экземпляр объекта XMLHttpRequest. Это довольно просто в JavaScript; вы используете ключевое слово new с именем объекта, как показано в листинге 1.

Листинг 1. Создание нового объекта XMLHttpRequest
<script language="javascript" type="text/javascript">
var request = new XMLHttpRequest();
</script>

Это совсем не тяжело, не правда ли? Помните, что JavaScript не требует типизации своей переменной, поэтому вам не нужно ничего из того, что вы видите в листинге 2 (возможно, так бы вы создавали этот объект на Java).

Листинг 2. Псевдо-код Java для создания XMLHttpRequest
XMLHttpRequest request = new XMLHttpRequest();

Итак, вы создаете переменную в JavaScript при помощи var, даете ей имя (например, "request"), и присваиваете ей новый экземпляр XMLHttpRequest. На данном этапе вы готовы использовать объект в ваших функциях.

Обработка ошибок

В реальной жизни вещи могут портиться, и этот код не обеспечивает никакой обработки ошибок. Несколько лучший способ – создать этот объект и дать ему грациозно потерпеть неудачу, если что-либо пойдет не так. Например, многие старые браузеры (верите или нет, люди все еще используют старые версии Netscape Navigator) не поддерживают XMLHttpRequest, и вам нужно предупредить таких пользователей, что что-то не получилось. В листинге 3 показано, как можно создать этот объект так, что если что-то поломается, он покажет предупреждение на JavaScript.

Листинг 3. Создание XMLHttpRequest с некоторыми способностями обработки ошибок
<script language="javascript" type="text/javascript">
var request = false;
try {
  request = new XMLHttpRequest();
} catch (failed) {
  request = false;
}

if (!request)
  alert("Error initializing XMLHttpRequest!");
</script>

Убедитесь, что вы понимаете каждый из следующих шагов:

  1. Создайте новую переменную с именем request и присвойте ей значение false. Вы будете использовать false как условие, означающее, что объект XMLHttpRequest еще не был создан.
  2. Добавьте блок try/catch:
    1. Попробуйте создать объект XMLHttpRequest.
    2. Если это не удалось (catch (failed)), удостоверьтесь, что request все еще равен false.
  3. Проверьте и узнайте, равно ли false значение request (если все прошло нормально, этого не случится).
  4. Если была проблема (и request равна false), используйте предупреждение JavaScript для сообщения пользователю о возникновении проблемы.

Это довольно просто; у большинства JavaScript- и Web-разработчиков больше времени уходит на чтение и написание, чем на понимание. Теперь у вас есть защищенный от ошибок код, создающий объект XMLHttpRequest и даже предупреждающий вас о том, что что-то прошло не так.

Работа с Microsoft

Это все выглядит довольно хорошо... по крайней мере, пока вы не попробуете этот код в Internet Explorer. Если вы это сделаете, то получите что-то, выглядящее так же ужасно, как на рисунке 1.

Рисунок 1. Internet Explorer отображает ошибку
Рисунок 1. Internet Explorer отображает ошибку

Microsoft играет по правилам?

Много было написано об Ajax и увеличивающемся интересе Microsoft к присутствию в этой области. Действительно ожидается, что новейшая версия Internet Explorer (версия 7.0, намеченная к выходу в 2006) будет поддерживать XMLHttpRequest напрямую, позволяя вам использовать ключевое слово new вместо всего кода создания Msxml2.XMLHTTP. Но не слишком обольщайтесь; вам все равно необходимо поддерживать старые браузеры, поэтому кросс-браузерный код уйдет со сцены еще не скоро.

Понятно, что что-то не работает; Internet Explorer – это тяжелый устаревший браузер, но около 70% пользователей его используют. Другими словами, вы не сделаете много в Web-мире, если не будете поддерживать Microsoft и Internet Explorer! Поэтому нужен другой метод работы с браузерами Microsoft.

Оказывается, что Microsoft поддерживает Ajax, но называет свою версию XMLHttpRequest по-другому. Вернее он называет этот объект несколькими разными именами. Если вы используете более новую версию Internet Explorer, то должны использовать объект Msxml2.XMLHTTP; некоторые старые версии Internet Explorer используют Microsoft.XMLHTTP. Вы должны поддерживать оба типа объектов (без потери уже имеющейся поддержки браузеров не от Microsoft). В листинге 4 приведен уже знакомый код с добавленной поддержкой Microsoft.

Листинг 4. Добавление поддержки для браузеров Microsoft
<script language="javascript" type="text/javascript">
var request = false;
try {
  request = new XMLHttpRequest();
} catch (trymicrosoft) {
  try {
    request = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (othermicrosoft) {
    try {
      request = new ActiveXObject("Microsoft.XMLHTTP");
    } catch (failed) {
      request = false;
    }
  }
}

if (!request)
  alert("Error initializing XMLHttpRequest!");
</script>

Легко затеряться в фигурных скобках, поэтому рассмотрим поэтапно все действия:

  1. Создайте новую переменную с именем request и присвойте ей значение false. Вы будете использовать false как условие, означающее, что объект XMLHttpRequest еще не был создан.
  2. Добавьте блок try/catch:
    1. Попробуйте создать объект XMLHttpRequest.
    2. Если это не удалось (catch (trymicrosoft)):
      1. Попробуйте создать совместимый с Microsoft объект, используя более новые версии Microsoft (Msxml2.XMLHTTP).
      2. Если это не удалось (catch (othermicrosoft)), попробуйте создать совместимый с Microsoft объект, используя старые версии Microsoft (Microsoft.XMLHTTP).
    3. Если это не удалось (catch (failed)), удостоверьтесь, что request все еще равен false.
  3. Проверьте и узнайте, равно ли еще false значение request (если все прошло нормально, этого не случится).
  4. Если была проблема (и request равна false), используйте предупреждение JavaScript для сообщения пользователю о возникновении проблемы.

Выполните эти изменения в своем коде и попробуйте снова выполнить его в Internet Explorer; Вы должны увидеть созданную вами форму (без сообщения об ошибке). В моем случае результат выглядел так, как показано на рисунке 2.

Рисунок 2. Internet Explorer работает нормально
Рисунок 2. Internet Explorer работает нормально

Статика против динамики

Взгляните снова на листинги 1, 3 и 4 и обратите внимание, что весь этот код непосредственно вложен внутри тегов сценария. Когда JavaScript закодирован подобным образом и не размещен внутри тела метода или функции, он называется статическим JavaScript. Это означает, что код выполняется в какой-то момент времени до того, как страница отобразится пользователю (из спецификации не ясно с точностью 100%, когда этот код выполняется, и браузеры поступают по-разному; но все же есть гарантия, что код выполняется до того, как пользователи смогут взаимодействовать с вашей страницей). Обычно большинство Ajax-программистов именно так и создают объект XMLHttpRequest.

Как было сказано, вы определенно можете поместить этот код в метод в соответствии с листингом 5.

Листинг 5. Перемещение кода создания XMLHttpRequest в метод
<script language="javascript" type="text/javascript">

var request;

function createRequest() {
  try {
    request = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (othermicrosoft) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (failed) {
        request = false;
      }
    }
  }

  if (!request)
    alert("Error initializing XMLHttpRequest!");
}
</script>

Поступив так, вы должны вызвать этот метод до начала любой работы с Ajax. То есть, вы должны иметь что-то похожее на листинг 6.

Листинг 6. Использование метода для создания XMLHttpRequest
<script language="javascript" type="text/javascript">

var request;

function createRequest() {
  try {
    request = new XMLHttpRequest();
  } catch (trymicrosoft) {
    try {
      request = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (othermicrosoft) {
      try {
        request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (failed) {
        request = false;
      }
    }
  }

  if (!request)
    alert("Error initializing XMLHttpRequest!");
}

function getCustomerInfo() {
  createRequest();
  // Сделать что-то с переменной request 
}
</script>

Единственное замечание по поводу этого кода (и причина того, почему многие Ajax-программисты не используют такой подход) – он задерживает вывод сообщения об ошибке. Представьте, что у вас имеется сложная форма с 10 или 15 полями, рамками выбора вариантов и т.п., и вы активизируете какой-либо Ajax-код при вводе пользователем текста в поле 14 (внизу формы). Активизируется getCustomerInfo(), который пытается создать объект XMLHttpRequest, и (для данного примера) терпит неудачу. Пользователю выводится предупреждение, сообщающее о том (во многих словах), что он не может использовать это приложение. Но пользователь уже потратил время для заполнения формы! Это довольно неприятно, и эта неприятность не входит в число тех вещей, из-за которых пользователь захотел бы вернуться на ваш сайт.

В случае использования статического JavaScript, пользователь получит сообщение об ошибке, как только зайдет на вашу страницу. Это так же неприятно? Возможно. Можно взбесить пользователя тем, что ваше Web-приложение не работает в его браузере. Однако это определенно лучше, чем выдать эту же ошибку после 10 минут ввода пользователем информации. Только по этой причине я советую вам устанавливать ваш код статически и заранее предупреждать пользователей о возможных проблемах.


Передача запросов с XMLHttpRequest

После того как вы создали объект request, можете начать цикл запрос/ответ. Вспомните, что единственной целью объекта XMLHttpRequest является предоставление вам возможности посылать запросы и получать ответы. Все остальное (изменение пользовательского интерфейса, замена изображений, даже интерпретация переданных сервером данных) – это работа JavaScript, CSS или другого кода на ваших страницах. С готовым к использованию XMLHttpRequest вы можете сейчас послать запрос серверу.

Добро пожаловать в "песочницу"

Ajax имеет модель безопасности по типу "песочницы". В результате ваш Ajax-код (и, в частности, объект XMLHttpRequest) может посылать запросы только тому домену, на котором выполняется. Вы подробнее узнаете о безопасности и Ajax в следующей статье, а теперь просто запомните, что код, выполняющийся на вашем локальном компьютере, может выполнять запросы только к серверным сценариям на вашем локальном компьютере. Если ваш Ajax-код работает на www.breakneckpizza.com, он должен посылать запросы к сценариям, работающим на www.breakneckpizza.com.

Установка URL сервера

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

Листинг 7. Создание URL-запроса
<script language="javascript" type="text/javascript">
   var request = false;
   try {
     request = new XMLHttpRequest();
   } catch (trymicrosoft) {
     try {
       request = new ActiveXObject("Msxml2.XMLHTTP");
     } catch (othermicrosoft) {
       try {
         request = new ActiveXObject("Microsoft.XMLHTTP");
       } catch (failed) {
         request = false;
       }  
     }
   }

   if (!request)
     alert("Error initializing XMLHttpRequest!");

   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
   }
</script>

Ничто не должно быть здесь не понятным для вас. Сначала код создает новую переменную phone и присваивает ей значение поля формы, имеющего ID "phone". В листинге 8 приведен XHTML для этой конкретной формы, в которой вы можете увидеть поле field и его атрибут id.

Листинг 8. Форма "Break Neck Pizza"
 <body>
  <p><img src="breakneck-logo_4c.gif" alt="Break Neck Pizza" /></p>
  <form action="POST">
   <p>Enter your phone number:
    <input type="text" size="14" name="phone" id="phone" 
           onChange="getCustomerInfo();" />
   </p>
   <p>Your order will be delivered to:</p>
   <div id="address"></div>
   <p>Type your order in here:</p>
   <p><textarea name="order" rows="6" cols="50" id="order"></textarea></p>
   <p><input type="submit" value="Order Pizza" id="submit" /></p>
  </form>
 </body>

Обратите также внимание, что когда пользователи вводят телефонный номер или изменяют номер, вызывается метод getCustomerInfo(), приведенный в листинге 8. Затем этот метод собирает этот номер и использует его для составления строки URL, сохраняемой в переменной url. Помните: поскольку Ajax-код выполняется в "песочнице" и может соединяться только с тем же самым доменом, вам фактически не нужно указывать домен в URL. В данном примере имя сценария - /cgi-local/lookupCustomer.php. Наконец, номер телефона добавляется к этому сценарию как параметр GET: "phone=" + escape(phone).

Для тех, кто никогда прежде не видел метод escape(), я поясню - он используется для перевода в другое представление любого символа, который не может быть передан корректно в виде простого текста. Например, все пробелы в номере телефона преобразуются в символы %20, что дает возможность передачи этих символов в URL.

Вы можете добавить столько параметров, сколько нужно. Например, если вы хотите добавить еще один параметр, просто добавьте его к URL и разделите параметры символом амперсанда (&) [первый параметр отделяется от имени сценария знаком вопроса (?)].

Открытие запроса

Открывает ли open()?

Internet-разработчики не пришли к согласию насчет того, что точно делает метод open(). Чего он действительно не делает, так это не открывает запрос. Если бы вы проследили за сетью и передачей данных между вашей XHTML/Ajax-страницей и сценарием, с которым она связана, то не увидели бы никакого трафика при вызове метода open(). Непонятно почему было выбрано именно это имя, но ясно, что это не лучший выбор.

Имея URL, вы можете сконфигурировать запрос. Это делается при помощи метода open() вашего объекта XMLHttpRequest. Этот метод принимает пять параметров:

  • request-type: Тип передаваемого запроса. Обычными значениями являются GET или POST, но вы можете также передать HEAD-запросы.
  • url: URL для соединения.
  • asynch: True, если вы хотите, чтобы запрос был асинхронным, и false, если он должен быть синхронным. Этот параметр необязателен и по умолчанию равен true.
  • username: Если требуется аутентификация, то здесь вы можете указать имя пользователя. Это необязательный параметр и он не имеет значения по умолчанию.
  • password: Если требуется аутентификация, здесь вы можете указать пароль. Это необязательный параметр и он не имеет значения по умолчанию.

Обычно вы будете использовать три первых параметра. На самом деле, даже если вы хотите асинхронный запрос, желательно указывать "true" в качестве третьего параметра. Это значение по умолчанию, но это приятная мелочь для самодокументирования. Всегда видно, асинхронный запрос или нет.

Объедините все это вместе, и у вас, скорее всего, получится примерно такая строка, какая показана в листинге 9.

Листинг 9. Открытие запроса
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
   }

Как только вы вычислили URL, все становится тривиальным. Для большинства запросов использования GET достаточно (в будущих статьях вы увидите ситуации, в которых, возможно, захотите использовать POST); это все, что вам нужно для использования open().

Головоломка по асинхронии

В последующей статье этой серии я потрачу значительную часть времени на написание и использование асинхронного кода, но вы должны иметь представление о том, почему последний параметр в open() настолько важен. В обычной модели запрос/ответ (здесь думайте о Web 1.0) клиент (ваш браузер или код, выполняющийся на вашей локальной машине) посылает запрос серверу. Этот запрос является синхронным; другими словами, клиент ждет ответа от сервера. Пока клиент ждет, вы обычно получаете одну из нескольких форм уведомления о том, что вы ждете:

  • Песочные часы (главным образом в Windows).
  • Крутящийся мяч (обычно на Mac-компьютерах).
  • Приложение замирает и иногда меняется курсор.

Именно это дает ощущение, что Web-приложения неуклюжи или медленны – отсутствие реальной интерактивности. Когда вы нажимаете кнопку, ваше приложение главным образом становится непригодным к использованию, пока не будет получен ответ на переданный только что запрос. Если вы пошлете запрос, требующий интенсивных вычислений на сервере, время ожидания может быть значительным (по крайней мере, для сегодняшнего мира, использующего много процессоров, DSL и не привыкшего ждать).

Асинхронный запрос не ждет ответа от сервера. Вы посылаете запрос, и ваше приложение продолжает работу. Пользователи могут вводить данные в Web-форму, нажимать другие кнопки, даже уйти с формы. Нет крутящегося мяча или вращающихся песочных часов, и нет больших замираний приложения. Сервер молча отвечает на запрос, и когда завершается передача ответа, он дает знать об этом инициатору запроса (способами, о которых вы вскоре узнаете). В результате мы имеем приложение, которое не выглядит неуклюжим или медленным, а, наоборот, чувствительным, интерактивным и быстрым. Это только один компонент Web 2.0, но это очень важный компонент. Любые прекрасные GUI-компоненты и парадигмы Web-дизайна не могут преодолеть медленную, синхронную модель запрос/ответ.

Передача запроса

После настройки запроса с использованием open() вы готовы к передаче запроса. К счастью, метод для передачи запроса назван более правильно, чем open(); его просто назвали send().

send() принимает только один параметр – содержимое для передачи. Но прежде чем вы серьезно задумаетесь об этом, вспомните, что вы уже передали данные в самом URL:

var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

И хотя вы можете передать данные с использованием send(), вы можете также передать их через сам URL. В действительности в GET-запросах (которые будут составлять до 80% типичного использования Ajax) намного легче передавать данные в URL. Когда вы начинаете передавать защищенную информацию или XML, нужно рассмотреть передачу содержимого через send() (я рассмотрю защищенные данные и обмен XML-сообщениями в последующей статье этой серии). Если вам не надо передавать данные через send(), установите аргумент в значение null. Для передачи запроса в примере, используемом в данной статье, это именно то, что нам нужно (см. листинг 10).

Листинг 10. Передача запроса
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.send(null);
   }

Указание метода обратного вызова

До сих пор вы сделали очень немного такого, что являлось бы чем-то новым, революционным или асинхронным. Маленькое ключевое слово "true" в методе open() устанавливает асинхронный запрос. На стороне сервера код пишется на Java-сервлетах и JSP, PHP или Perl. Так в чем же большой секрет Ajax или Web 2.0? Секрет вертится вокруг простого свойства XMLHttpRequest с названием onreadystatechange.

Прежде всего, вы должны понимать процесс, который вы создали в этом коде (пересмотрите листинг 10 при необходимости). Запрос настраивается и затем передается. Кроме того, поскольку это асинхронный процесс, JavaScript-метод (getCustomerInfo() в примере) не будет ожидать сервер. Поэтому код будет продолжать выполняться; в данном случае это означает, что произойдет выход из метода и управление вернется в форму. Пользователь может продолжить ввод информации, и приложение не будет ожидать сервер.

При этом возникает интересный вопрос: что происходит, когда сервер завершит обработку запроса? Ответ, по крайней мере, для имеющегося у нас на данный момент времени кода, - ничего! Очевидно, это не хорошо; следовательно, сервер должен иметь инструкцию некоего типа о том, что делать, когда он завершит обработку запроса, переданного ему объектом XMLHttpRequest.

Ссылка на функцию в JavaScript

JavaScript является слабо типизированным языком, и в переменной вы можете ссылаться почти на все, что угодно. То есть, если вы объявили функцию с названием updatePage(), JavaScript также рассматривает имя этой функции как переменную. Другими словами, вы можете ссылаться на функцию в вашем коде как на переменную с названием updatePage.

Здесь выходит на сцену свойство onreadystatechange. Это свойство позволяет вам указать метод обратного вызова (callback). Метод обратного вызова позволяет серверу (можете догадаться?) выполнить обратный вызов в коде вашей Web-страницы. Это добавляет в сервер уровень контроля; когда сервер завершает обработку запроса, он ищет объект XMLHttpRequest и, в частности, его свойство onreadystatechange. Затем активизируется любой метод, указанный в этом свойстве. Он называется методом обратного вызова, потому что сервер инициирует вызов в обратном направлении (в Web-странице) независимо от того, что происходит в самой Web-странице. Например, он может вызвать этот метод тогда, когда пользователь сидит в кресле, не трогая клавиатуру; однако, он может вызвать этот метод и тогда, когда пользователь вводит информацию, перемещает мышку, выполняет скроллинг страницы, нажимает кнопку... не важно, что делает пользователь.

Вот здесь и проявляет себя асинхронность: пользователь работает с формой на одном уровне, тогда как на другом уровне сервер отвечает на запрос и, затем, активизирует метод обратного вызова, указанный в свойстве onreadystatechange. Поэтому вы должны указать этот метод в вашем коде, как показано в листинге 11.

Листинг 11. Установка метода обратного вызова
   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }

Обратите особое внимание на то, где в коде устанавливается это свойство – перед вызовом send(). Вы должны установить это свойство перед передачей запроса, поэтому сервер может просмотреть его после завершения обработки запроса. Все, что нам осталось – написать updatePage(), что и рассматривается в последнем разделе этой статьи.


Обработка ответов сервера

Вы передали запрос, ваш пользователь с удовольствием работает с Web-формой (в то время, когда сервер обрабатывает запрос), и теперь сервер завершает обработку запроса. Сервер просматривает свойство onreadystatechange и определяет, какой вызвать метод. После этого вы можете представлять свое приложение как любое другое приложение, асинхронное или нет. Другими словами, вы не должны принимать каких-либо специальных действий по написанию методов, реагирующих на сервер; просто измените форму, перенаправьте пользователя на другой URL, или сделайте все что хотите, реагируя на переданный из сервера ответ. В этом разделе мы остановимся на реакции на ответ от сервера и выполним обычное действие – изменим на лету часть формы, которую видит пользователь.

Обратные вызовы и Ajax

Вы уже увидели, как дать серверу знать, что делать после завершения обработки запроса: установить свойство onreadystatechange объекта XMLHttpRequest в имя функции, которая будет выполнена. Затем, когда сервер обработает запрос, он автоматически вызовет эту функцию. Вы также не должны беспокоиться о каких-либо параметрах этого метода. Начнем с простого метода, аналогичного показанному в листинге 12.

Листинг 12. Код метода обратного вызова
<script language="javascript" type="text/javascript">
   var request = false;
   try {
     request = new XMLHttpRequest();
   } catch (trymicrosoft) {
     try {
       request = new ActiveXObject("Msxml2.XMLHTTP");
     } catch (othermicrosoft) {
       try {
         request = new ActiveXObject("Microsoft.XMLHTTP");
       } catch (failed) {
         request = false;
       }  
     }
   }

   if (!request)
     alert("Error initializing XMLHttpRequest!");

   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }

   function updatePage() {
     alert("Server is done!");
   }
</script>

Он просто выводит предупреждение о том, что сервер закончил свою работу. Попробуйте этот код на своей собственной странице, сохраните страницу и откройте ее в браузере (если вы хотите взять XHTML из этого примера, обратитесь к листингу 8). Когда вы введете номер телефона и покинете поле, вы должны увидеть всплывающее окно (см. рисунок 3); нажмите кнопку ОК и окно всплывет снова... и снова.

Рисунок 3. Ajax-код вывода всплывающего предупреждения
Ajax code popping up an alert

В зависимости от вашего браузера вы получите два, три или даже четыре предупреждения, прежде чем форма прекратит отображать всплывающее окно. Что же происходит? Вы не приняли во внимание состояние готовности HTTP, важного компонента цикла запрос/ответ.

Состояния готовности HTTP

Раньше я говорил, что сервер, закончив обработку запроса, ищет, какой метод вызвать в свойстве onreadystatechange объекта XMLHttpRequest. Это правда, но это не вся правда. На самом деле он вызывает этот метод каждый раз, когда меняется состояние готовности HTTP. Что это значит? Хорошо, вы сначала узнаете об этих состояниях.

Состояние готовности HTTP отображает состояние или статус запроса. Оно используется для определения того, начат ли запрос, принимается ли ответ, или завершен ли цикл запрос/ответ. Это состояние помогает также при определении того, можно ли читать текст или данные, которые сервер мог предоставить в ответе. Вы должны знать о пяти состояниях готовности в ваших Ajax-приложениях:

  • 0: Запрос не инициализирован (перед вызовом open()).
  • 1: Запрос инициализирован, но не был передан (перед вызовом send()).
  • 2: Запрос был передан и обрабатывается (на данном этапе вы можете обычно получить заголовки содержимого из ответа).
  • 3: Запрос обрабатывается; часто в ответе доступны некоторые частичные данные, но сервер не закончил свой ответ.
  • 4: Ответ завершен; вы можете получить ответ сервера и использовать его.

Как и почти все кросс-браузерные проблемы, эти состояния готовности используются несколько непоследовательно. Вы могли бы ожидать, что состояния всегда изменяются от 0 к 1, от 1 к 2, от 2 к 3, от 3 к 4, но на практике это редкий случай. Некоторые браузеры никогда не выдают 0 или 1, а сразу перепрыгивают к 2, затем к 3 и 4. Другие браузеры выдают все состояния. Но некоторые выдают состояние 1 несколько раз. Как вы видели в последнем разделе, сервер вызывал updatePage() несколько раз, и каждый вызов приводил к появлению всплывающего окна предупреждения – наверное, это не то, чего вы хотели!

Для Ajax-программирования единственное состояние, с которым вы должны иметь дело, - это состояние готовности 4, указывающее на то, что ответ сервера завершен и можно проверить ответные данные и использовать их. Для учета этого первая строка в вашем методе обратного вызова должна быть такой, как показано в листинге 13.

Листинг 13. Проверка состояния готовности
   function updatePage() {
     if (request.readyState == 4)
       alert("Server is done!");
   }

Это изменение проверяет, действительно ли сервер завершил процесс. Попробуйте поработать с этой версией кода, и вы должны получить только одно предупреждающее сообщение, как и должно быть.

Коды состояния HTTP

Несмотря на явный успех кода в листинге 13, есть одна проблема – что если сервер ответил на ваш запрос и завершил обработку, но возвращает ошибку? Вспомните, ваш серверный код не знает, был ли вызван он Ajax-приложением, JSP-страницей, обычной HTML-формой или любым другим типом кода; он имеет только традиционные Web-методы оповещения. И в Web-мире HTTP-коды могут иметь дело с различными вещами, которые могут возникнуть в запросе.

Например, вы составили запрос к URL, ввели URL неправильно и получили код ошибки 404, указывающий, что страница отсутствует. Это один из многих кодов состояния, которые могут получать HTTP-запросы (ссылка на полный список кодов состояния приведен в разделе Ресурсы). Также распространенными являются коды 403 и 401, указывающие на то, что запрашиваются защищенные или запрещенные данные. В каждом из этих случаев есть коды, которые приходят в ответе. Другими словами, сервер выполнил запрос (означает, что состояние готовности HTTP равно 4), но, возможно, не возвращает данные, ожидаемые клиентом.

То есть, кроме состояния готовности вы должны также проверять HTTP-состояние. Вам подходит код состояния 200, просто означающий, что все в порядке. С состоянием готовности 4 и кодом состояния 200 вы готовы обработать серверные данные, и эти данные должны быть теми, которые вы запрашивали (и нет ошибок или другой проблемной информации). Добавьте еще одну проверку в ваш метод обратного вызова, как показано в листинге 14.

Листинг 14. Проверка кода состояния HTTP
   function updatePage() {
     if (request.readyState == 4)
       if (request.status == 200)
         alert("Server is done!");
   }

Для более устойчивой обработки ошибок (с минимальным усложнением кода) вы можете добавить проверку или две для других кодов состояния; Посмотрите на модифицированную версию updatePage() в листинге 15.

Листинг 15. Добавление некоторой обработки ошибок
   function updatePage() {
     if (request.readyState == 4)
       if (request.status == 200)
         alert("Server is done!");
       else if (request.status == 404)
         alert("Request URL does not exist");
       else
         alert("Error: status code is " + request.status);
   }

Теперь измените URL в getCustomerInfo() в несуществующий URL и посмотрите, что произойдет. Вы должны увидеть предупреждение, говорящее о том, что запрошенный вами URL не существует – отлично! Очень трудоемко обрабатывать все ошибки, но это простое изменение решит 80 % проблем, которые могут возникнуть в обычном Web-приложении.

Чтение текста ответа

Теперь, когда вы убеждены, что запрос полностью обработан (через состояние готовности) и сервер вернул нормальный положительный ответ (через код состояния), можно, наконец, заняться данными, переданными от сервера. Они удобно сохранены в свойстве responseText объекта XMLHttpRequest.

Подробности о тексте в responseText, в терминах формата или длины, преднамеренно оставлены неопределенными. Это позволяет серверу установить этот текст виртуально во все что угодно. Например, один сценарий может возвратить разделенные запятыми значения, другой – разделенные вертикальной чертой значения (символ "|"), а третий может возвратить одну длинную строку текста. Это решает сервер.

В примере этой статьи сервер возвращает последний заказ клиента и его адрес, разделенные символом вертикальной черты. Заказ и адрес используются для установки элементов формы; в листинге 16 приведен код, обновляющий дисплей.

Листинг 16. Работа с ответом сервера
   function updatePage() {
     if (request.readyState == 4) {
       if (request.status == 200) {
         var response = request.responseText.split("|");
         document.getElementById("order").value = response[0];
         document.getElementById("address").innerHTML =
           response[1].replace(/\n/g, "
");
} else alert("status is " + request.status); } }

Сначала извлекается responseText и разделяется на части по символам вертикальной строки ("|") при помощи метода JavaScript split(). Получившийся массив значений присваивается массиву response. Первое значение (последний заказ клиента) находится в response[0] и устанавливается как значение поля с ID "order." Второе значение массива, response[1], является адресом клиента и требует несколько большей обработки. Поскольку строки в адресе разделены обычными разделителями строк (символ "\n"), нужно заменить их XHTML-разделителями строк <br />. Это выполняется при помощи функции replace() с регулярным выражением. Наконец, модифицированный текст устанавливается как inner HTML элемента div в форме HTML. В результате этого форма неожиданно обновляется пользовательской информацией, как показано на рисунке 4.

Рисунок 4. Форма Break Neck после извлечения пользовательских данных
Рисунок 4. Форма Break Neck после извлечения пользовательских данных

Перед заключением рассмотрим еще одно важное свойство XMLHttpRequest - responseXML. Это свойство содержит (догадались?) XML-ответ в случае, если сервер решил ответить в XML-формате. Работа с XML ответом существенно отличается от работы с обычным неформатированным текстом и включает работу с синтаксическим анализатором, Document Object Model (DOM) и некоторые другие соображения. Более подробно о работе с XML вы узнаете из будущей статьи. Пока же, поскольку responseXML обычно рассматривается при обсуждении responseText, мы просто упомянем его. Для многих простых Ajax-приложений responseText – это все что вам нужно, но вскоре вы научитесь работать также и с XML в Ajax-приложениях.


В заключение

Вы, возможно, уже немножко устали от XMLHttpRequest – я редко читаю целую статью об одном объекте, особенно если он простой. Однако вы будете использовать этот объект снова и снова в каждой странице и приложении, которое пишете с использованием Ajax. По правде говоря, осталось еще кое-что, что нужно сказать об XMLHttpRequest. В последующих статьях вы научитесь использовать в ваших запросах POST в дополнение к GET, устанавливать и читать заголовки содержимого в ваших запросах и ответах сервера; вы научитесь кодировать ваши запросы и даже работать с XML в вашей модели запрос/ответ.

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

Поэтому не игнорируйте эти детали и не просматривайте их беглым взглядом; когда ваша инструментальная программа создаст ошибку, вы не застрянете на ней, почесывая голову и посылая электронные письма в службу поддержки. Со знанием методов непосредственного использования XMLHttpRequest вы обнаружите, насколько легко отлаживать и исправлять даже самые странные ошибки. Инструментарий хорош тогда, когда вы можете полностью рассчитывать на него в решении всех ваших проблем.

Поэтому чувствуйте себя комфортно с XMLHttpRequest. Даже если у вас есть работающий Ajax-код, который был создан с использованием инструментальной программы, попробуйте переписать его, используя только объект XMLHttpRequest и его свойства и методы. Это будет отличной тренировкой и, возможно, поможет вам намного лучше понять, что происходит.

В следующей статье вы еще глубже проникнете в этот объект, исследуя некоторые из его хитрых свойств (например, responseXML), а также узнаете, как использовать POST-запросы и передавать данные в нескольких различных форматах. Итак, начнем кодирование.

Ресурсы

Научиться

Получить продукты и технологии

  • Книга Элизабет Фримен, Эрика Фримена и Брета Маклафлина Head Rush Ajax (O'Reilly Media, Inc., февраль 2006) берет идеи, выделенные в этой статье, и загружает их в вашу голову в стиле "Head First" (Вперед с головой).
  • Java и XML, вторая редакция, Брет Маклафлин (Август 2001, O'Reilly Media, Inc.) - содержит обсуждение автором преобразований XHTML и XML.
  • JavaScript: Полное руководство, Дэвид Флэнаган (ноябрь 2001, O'Reilly Media, Inc.) – содержит исчерпывающие инструкции по работе с JavaScript, динамическими Web-страницами, а в готовящемся издании добавлены две главы по Ajax.
  • Вперед головой в HTML с CSS и XHTML, Элизабет и Эрик Фримены (декабрь 2005, O'Reilly Media, Inc.) – полный источник информации о XHTML и CSS, а также о том, как их объединить.

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, XML, Технология Java
ArticleID=109183
ArticleTitle=Освоение Ajax: Часть 2. Выполнение асинхронных запросов с JavaScript и Ajax
publish-date=01172006