Научный консалтинг
Главная
Контакты
Номер телефона
Как мы работаем
Гарантии
Условия
Цены

Что такое AJAX?

Это Асинхронный Javascript и XML. Есть еще, кстати, AJAJ, который расшифровывается как Асинхронный Javascript и JSON.

В нашей статье приведен рабочий пример простейшего «Приложения AJAX» и, самое главное, детально рассмотрена асинхронность этой технологии, ее влияние на порядок выполнения кода.

Методология AJAX  известна достаточно давно, начиная с 1998-1999 гг., будучи впервые реализованной компанией Microsoft в браузере Internet Explorer. В настоящее время эта технология широко применяется для реализации клиент-серверного взаимодействия веб-страниц с серверами. Использование AJAX позволяет реализовать динамические html-страницы, которые могут обновляться сервером без их перезагрузки. Надо сказать, что, по-видимому, эта технология является одной из двух наиболее употребительных и целесообразных для применения в разработках сайтов и веб-приложений.

Вторая по значимости технология – это веб-сокеты – sockets. Остальные (в т.ч. Comet, к примеру) можно не принимать во внимание как уже неактуальные и/или устаревшие.

Что такое AJAX, как она работает – об этом написано множество книг, ей посвящено немало страниц сайтов. Например, этот сайт. Изложение достаточно подробное, актуальное, поэтому, вероятно, нет необходимости его дублировать. Здесь же мы поговорим об особенностях технологии AJAX, т.е. о некоторых тонкостях.

Не всем понятен термин «асинхронный». Асинхронно – это означает «вне зависимости от чего-либо». В данном случае – от основного кода программы (на javascript), который выполняется на веб-странице. Как известно, в настоящее время (конец 2016 г.) ВСЕ приложения, программные коды на javascript являются однопоточными. Т.е. невозможно запустить параллельно несколько одновременно выполняющихся кодов javascript, выполнить их возможно лишь по очереди.

Так вот, асинхронность предоставляет, своего рода, подвид «многопоточности», если так можно выразиться. Правда, это – особая «многопоточность», когда код в асинхронном режиме выполняется вообще НЕЗАВИСИМО от остального кода. Т.е. он выполнится, но вот когда – да кто же его знает… Видимо, тогда, когда браузер сочтет нужным его выполнить. Иногда в связи с этим возникают курьезы.

Перейдем к делу

Для реализации AJAX необходим, во-первых, соответствующий код на javascript, посылающий запрос на сервер. Естественно, необходимо серверное приложение (и сам сервер, естественно, который должен быть доступным для сетевого взаимодействия) или, проще говоря, некоторая программа, которая способна принимать запросы от javascript, расположенного на странице и отсылать ей те или иные ответы. Серверная программа может быть написана, к примеру, на РНР или Perl – в случае, если она не слишком сложна и скорость ее работы не является критичной. В противоположном случае целесообразно использовать более быстродействующие языки, например, С++ (или C#, если речь идет о платформе Windows).

Для дальнейшего рассмотрения будем использовать коды простых тестовых файлов, при помощи которых можно продемонстрировать работу технологии AJAX.


Файл AJAX_test.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Тест AJAX</title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251"/>
<link href="http://www.dissertacii-diplom-ufa.ru/comments/lib/style_com.css" media="all" rel="stylesheet" type="text/css" />
</head>
<body style="padding:0; margin:0;"> <div>
   <div id="" style="border:1px solid; position:relative">
       <div id="error" style="color:red;"></div>
       <div id="error1" style="color:green;"></div>
       <div id="error2" style="color:blue;"></div>
   <form name="test1" class="comm_form" id="test" method="post" action="" onsubmit="delc('test'); return false;">
       <textarea name="" rows="" cols="">Текст</textarea><br />
       <input type="submit" value="Тест" />    </form>    </div> </div>
   <script src="AJAX_manage.js" charset="utf-8" type="text/javascript"></script>
</body>
</html>


Файл AJAX_manage.js


function delc(body){
var s;
var t0 = performance.now(); console.log("1="+t0);
   send_body(body);
t0 = performance.now(); console.log("2="+t0);
   setTimeout(function()
   {
t0 = performance.now(); console.log("3="+t0);
       document.getElementById("error1").innerHTML = document.getElementById("error").innerHTML;
       document.getElementById("error2").innerHTML =   sessionStorage.getItem("stuff", s);
   }, 0);
t0 = performance.now(); console.log("4="+t0);
// Очищаем хранилище сессий в браузере
sessionStorage.removeItem("stuff");
t0 = performance.now(); console.log("5="+t0);
function send_body(body) {
               var xhr = new XMLHttpRequest();
               xhr.timeout =10000;// Если время работы скрипта превысило 10 секунд, появляется событие  ontimeout
               xhr.ontimeout =function(){
               xhr.abort();
               xhr = null; //              alert("Превышено время ожидания скрипта. Возможно, сервер недоступен.");
               return "AJAX-error";
               };
t0 = performance.now(); console.log("6="+t0);
           try
{
t0 = performance.now(); console.log("7="+t0);
               xhr.open("POST",'manage.php?r='+Math.random(),true);
               xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
               xhr.onreadystatechange =function xhr_state()   {
t0 = performance.now(); console.log("8="+t0);
                 if(xhr.readyState !=4)return;
                 if(xhr.status ==200){
                     if(xhr.responseText !=1){
                      s = xhr.responseText;
                     sessionStorage.setItem("stuff", s);
                       document.getElementById("error").innerHTML = s;
t0 = performance.now();  console.log("9="+t0);
                     }else{
                       alert();
                     }
                 } else {
                   alert('xhr error \nВозможно, сервер недоступен\n'+xhr.statusText);
                 }
           xhr.send(body);
t0 = performance.now(); console.log("10="+t0);
}catch(err){alert("Невозможно соединиться с сервером:\n"+err.toString());}
       }
}



Файл manage.php

<?php
session_start();
header('Content-type: text/html; charset=windows-1251');
if(!isset($_SESSION['count'])) $_SESSION['count'] = 0;
if(!isset($_SESSION['s'])) $_SESSION['s'] = 0;
echo $s;
$_SESSION['count'] = $_SESSION['count'] +1;
echo "_SESSION['count']=".$_SESSION['count'];
$_SESSION['salt'] = $s;
if($_SESSION['count'] >3) {
unset($_COOKIE[session_name()]);
session_destroy(); }
die;
?>


На самом деле, три указанных файла представляют собой простое «приложение», которое работает на основе технологии AJAX. Суть состоит в том, что при нажатии на кнопку «Тест» в верхней части веб-страницы появляются сообщения от сервера вида
_SESSION['count']=3
Вместо цифры 3 могут последовательно фигурировать числа от 1 до 4. Эти цифры указывают на номер счетчика сессий, подсчет которым ведется на сервере. После достижения номера 4 он устанавливается равным 1 и вновь, при последующих обращениях к серверу, увеличивается.


Разберемся

Обратите внимание, что в файле AJAX_manage.js имеются многочисленные строчки вида
t0 = performance.now();  console.log("9="+t0);
Более подробно о функции performance.now() можно прочитать здесь. Если кратко, эта функция определяет текущее время, происшедшее в момента загрузки страницы. Здесь она используется для определения времени выполнения тех или иных участков кода javascript. В консоли затем это время отображается в виде цифр. Например, запись

6=1191712.7683983191

означает, что с момента загрузки страницы до (очередного) выполнения строчки под номером 6

t0 = performance.now();  console.log("6="+t0);

прошло 1191712.7683983191 миллисекунд времени.

Функция вида  console.log("9="+t0)записывает результаты измерения времени в консоль браузера, которую можно посмотреть – при желании.

Необходимость этого вызвана как раз пресловутой асинхронностью технологии AJAX.

К слову, есть, конечно, еще и СИНХРОННЫЙ AJAX. Для работы в синхронном режиме достаточно в команде

xhr.open("POST",'manage.php?r='+Math.random(),true)

изменить параметр true на false.

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

Также обратим внимание на функцию setTimeout. Она устанавливает таймаут. Судя по программному коду, он равен нулю.

Однако, помимо этого, данная функция еще и извлекает соответствующий участок кода, в данном случае тот, который находится в соответствующих фигурных скобках:

t0 = performance.now(); console.log("3="+t0);
       document.getElementById("error1").innerHTML = document.getElementById("error").innerHTML;
       document.getElementById("error2").innerHTML =   sessionStorage.getItem("stuff", s);

из общего потока выполнения и ставит в конец очереди программных кодов скрипта. Немного забегая вперед, отметим, что в данном случае это необходимо для корректной его работы.

Запускаем программу

Так, что же. Запускаем программу путем нажатия на кнопку «Тест». Можно сделать это несколько раз. Вверху страницы видим последовательно изменяющиеся надписи, навроде:

_SESSION['count']=2

затем, при следующем нажатии


_SESSION['count']=3
_SESSION['count']=2

затем

_SESSION['count']=4
_SESSION['count']=3

И т.д.

Мы видим, что все работает неверно. Во-первых, номера сессий для «красного» и «зеленого» блоков div не совпадают, отличаясь на 1. А ведь это заведомо неверно, так как данный программный код при каждом нажатии формирует ОДНО обращение к серверу. Следовательно, и ответ от него (заключающий в себе номер наименование переменной сессии сервера и ее номер), также должен быть в единственном числе. Если же мы посмотрим на html-кода «красного» и «зеленого» блоков, то увидим, что, по сути, текст для «зеленого» блока однозначно задается текстовым содержанием «красного» путем следующей команды:

document.getElementById("error1").innerHTML = document.getElementById("error").innerHTML;

здесь "error1" – идентификатор (id) «зеленого» блока, а "error" – идентификатор «красного». Т.е., казалось бы, их текстовое содержание, в результате работы скрипта должно строго совпадать. А оно, как видим, «отстает», причем ровно на 1.

Во-вторых, что-то незаметно признаков присутствия блока с id="error2", имеющего голубой цвет. Т.е. сессия, записываемая браузером (не путайте ее с сессией сервера!... как браузер, так и сервер могут иметь свои сессии), видимо… не сохраняется.

Хотя, вроде бы, вызовы

document.getElementById("error1").innerHTML = document.getElementById("error").innerHTML;

расположены ПОСЛЕ вызова функции send_body(body) и, по логике, должны бы выполняться ПОСЛЕ того, как она полностью выполнится. Тем более, что еще использован таймаут.

В чем дело

Для этого рассмотрим логи браузера. Например, в Firefox для этой цели достаточно открыть консоль. Вот здесь-то нам и пригодятся строчки вида

t0 = performance.now(); console.log("6="+t0);

Вот типичное содержание консоли:

Типичное содержание консоли при неправильной работе приложения AJAX

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

Несмотря на то, что строчки вида

t0 = performance.now(); console.log("6="+t0);

в коде JS пронумерованы последовательно, браузер, однако, выполняет их в другом порядке, а именно

1, 6, 7 /POST-запрос/, 10, 2, 4, 5, 3, 8, 8, 8, 9.

Мы видим, что порядок не соответствует «хронологическому». Давайте подумаем, почему так.

Вначале выполняется участок кода до

t0 = performance.now(); console.log("1="+t0);

Ну, это и понятно – самое начало скрипта. Затем идет вызов функции

send_body(body)

При этом, очевидно, выполняется участок кода с

t0 = performance.now(); console.log("6="+t0);

затем

t0 = performance.now(); console.log("7="+t0);

После чего начинается формирование POST-запроса и его выполнение. Обратим внимание, что обработчик

xhr.onreadystatechange

пока не выполняется. Он просто регистрируется в системе и «ждет своего часа». В принципе, по этой причине его можно разместить в любом месте javascript-кода.

После того, как запрос отправлен на сервер, выполняется строчка  

t0 = performance.now(); console.log("10="+t0);

и производится выход из функции send_body(body).

Следовательно, выполняются строчки

t0 = performance.now(); console.log("2="+t0);
t0 = performance.now(); console.log("4="+t0);
t0 = performance.now(); console.log("5="+t0);

Выполнение

sessionStorage.removeItem("stuff");

означает, что данные в сессии браузера… УДАЛЯЮТСЯ. Точнее, не все данные, а лишь те, которые соответствуют ключу под названием “stuff” (вообще, название ключа может быть, видимо, любым). Именно поэтому, в результате такого «обнуления», в «голубой» блок (с идентификатором id=“error2”) никаких данных не попадает, потому-то он и не отображается на странице.

Далее «почему-то» выполнилась строчка

t0 = performance.now(); console.log("3="+t0);

Сразу после которой браузер задает содержимое «зеленого» и «голубого» блоков тем значением, которое НА ДАННЫЙ момент содержится в «красном» блоке и в сессии браузера, соответственно. Однако, до сих пор содержимое «красного» блока не обновлялось. Посему, в «зеленый» блок попадает УСТАРЕВШЕЕ (т.е. записанное в него ранее) содержимое «красного» блока, а «голубой» блок не получает вообще ничего (так как ключ сессии "stuff", как уже говорилось, уже стерт, новое значение в нее пока еще не вносилось).

Таким образом, теперь понятно, почему номера сессий «красного» и «зеленого» блоков различаются на 1? Потому, что в красном содержится номер текущей сессии сервера, а в «зеленом» - ее предыдущий номер. Все оказалось просто.

Но, пока суть да дело, пока выполняется последовательно код javascript – начинает «подавать голос» сервер. Это мы видим по тому, что «заработало» событие xhr.onreadystatechange.

Мы видим подряд ТРИ вызова строчки

t0 = performance.now(); console.log("8="+t0);

Это вызвано тем, что объект XMLHttpRequest может иметь 4 основных состояния, в том числе три из них – рабочие:

  • 2 - Loaded
  • 3 - Interactive
  • 4 - Complete

Т.е. такие, нахождение в которых влекут изменение состояния обработчика.

Вот она – асинхронность в реальном виде. Пока сервер «молчал», браузер спокойно обрабатывал код javascript, как обычно. Но, как только возникло событие xhr.onreadystatechange, браузер тут же переключился на его обработку.

Повторимся, эта обработка происходит уже ПОСЛЕ того, как произведена запись содержимого «зеленого» блока, а также удаление данных сессии браузера.

Далее, окончательно, вызывается строчка

t0 = performance.now(); console.log("9="+t0);

При этом в «красный» блок (в процессе работы обработчика xhr.onreadystatechange) записывается только что полученное (новое) значение номера сессии сервера.

Вот такая асинхронность AJAX

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


Так что же делать?

Иными словами, как сделать работу программы корректной? Как сделать так, чтобы запись содержимого в «зеленый» и «голубой» блоки производилась ПОСЛЕ обработки ответа сервера и ДО удаления данных сессии браузера, соответственно?

Вот здесь и пригодится вызов setTimeout().

Он, как уже говорилось, позволяет, во-первых, отложить выполнение соответствующего кода на определенное количество времени (в миллисекундах), а также извлекает этот код из очереди и помещает в ее конец. Некая аналогия (правда, лишь образная) здесь наблюдается со свойством html position:absolute.

В самом деле, если стиль у какого-либо элемента будет установлен именно таким, то элемент будет извлечен из общего потока документа и будет размещен НЕЗАВИСИМО от остальных элементов (конечно, здесь не имеется в виду его возможные дополнительные параметры позиционирования).

Идея вот в чем: нам необходимо отсрочить выполнение строчки

t0 = performance.now(); console.log("3="+t0);

до тех пор, пока не произойдет и пока не будет обслужен ответ сервера.

Если писать программу «ориентировочно», то можно, конечно, задать некое время задержки, чтобы его «заведомо хватило». Скажем, отложить на несколько секунд. Однако, надо понимать, во-первых, что эта «заведомость» может быть в каждом случае разной. Во-вторых, трата нескольких секунд ПРОСТО ТАК (т.е. безо всяких обоснований) видится абсолютно неприемлемой. Ведь подобных операций в браузере могут быть десятки, если не сотни и тысячи. Суммарные потери времени могут быть очень велики.

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

Итак, необходимо сделать обоснованную задержку. Но, вначале посмотрим, насколько эти рассуждения соответствуют истине. В строке

   setTimeout(function()
   {
t0 = performance.now(); console.log("3="+t0);
       document.getElementById("error1").innerHTML = document.getElementById("error").innerHTML;
       document.getElementById("error2").innerHTML =   sessionStorage.getItem("stuff", s);
   }, 50);

вместо параметра «0» укажем значение «50». Т.е. задержка составит 50 миллисекунд. Что же, посмотрим на результаты работы программы:

Типичное содержание консоли при работе приложения AJAX с задержкой, равной 50 миллисекунд

Как видим, при первом же нажатии на кнопку «Тест» ВСЕ блоки бодро показывают одно и то же значение, равное текущему номеру сессии сервера (с учетом алгоритма его определения). Более того. появился и долгожданный «голубой» блок.

Строчка

t0 = performance.now(); console.log("3="+t0);

как ей и положено, выполняется теперь ПОСЛЕ все остальных строчек. Это означает, что запись содержимого в «голубой» и «зеленый» блоки, равно как и стирание данных сессии браузера (по соответствующему ключу)  производится уже ПОСЛЕ того, как получен и обработан ответ сервера. Т.е. после того, как в «красный» блок занесен (обновленный) номер текущей сессии сервера.

Однако, подбор времени в данном случае – это уже из серии «гадания на кофейной гуще». Как уже говорилось, подобное нельзя назвать приемлемым.

Естественно, это время необходимо задавать на основе вычислений. Ибо, в случаях, когда сервер отвечает быстро, оно и в самом деле может составлять несколько десятков миллисекунд. Тогда как в случае «проблемных» серверов время ответа может быть на порядки выше.

Однако, вероятно, будет невозможно изменить время задержки уже после того, как был установлен соответствующий setTimeout.

Оптимизация задержки

Итак, возможный алгоритм действий может быть таким. Запись данных в «голубой» и «зеленый» блоки должна быть произведена не ранее, чем сервер, наконец, ответит браузеру и последний запишет полученные данные (в нашем случае – номер сессии сервера) в «красный» блок. Т.е. не ранее момента, когда произойдет выполнение строчки

t0 = performance.now(); console.log("9="+t0);

Как только «миновали» этот участок кода, очевидно, больше обрабатывать данные сервера не нужно (если, конечно, кнопка «Тест» не будет нажата вновь). Это означает, что вызов указанной процедуры записи следует производить после этого участка.

Для этой цели код javascript следует изменить следующим образом (изменения показаны красным цветом увеличенным шрифтом):

function delc(body){
var s;
var t0 = performance.now();    console.log("1="+t0);
  send_body(body);
t0 = performance.now();    console.log("2="+t0);
  function write_data() {setTimeout(function()
  {
t0 = performance.now(); console.log("3="+t0);
     document.getElementById("error1").innerHTML = document.getElementById("error").innerHTML;
     document.getElementById("error2").innerHTML =  sessionStorage.getItem("stuff", s);
  }, 5);}
t0 = performance.now(); console.log("4="+t0);
// Очищаем хранилище сессий в браузере sessionStorage.removeItem("stuff");
t0 = performance.now(); console.log("5="+t0);
function send_body(body) {
           var xhr = new XMLHttpRequest();
           xhr.timeout = 10000; // Если время работы скрипта превысило 10 секунд, появляется событие  ontimeout
           xhr.ontimeout = function() {
           xhr.abort();
           xhr = null; //          alert("Превышено время ожидания скрипта. Возможно, сервер недоступен.");
           return "AJAX-error";
t0 = performance.now(); console.log("6="+t0);
        try
{
t0 = performance.now(); console.log("7="+t0);
           xhr.open("POST", 'manage.php?r='+Math.random(), true);
           xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
           xhr.onreadystatechange = function xhr_state()  {
t0 = performance.now(); console.log("8="+t0);
             if (xhr.readyState != 4) return;
             if (xhr.status == 200) {
                if(xhr.responseText != 1){
                 s = xhr.responseText;
                sessionStorage.setItem("stuff", s);
                 document.getElementById("error").innerHTML = s;
t0 = performance.now();  console.log("9="+t0);
                   write_data();
                }else{
                 alert();
                }
             } else {
              alert('xhr error \nВозможно, сервер недоступен\n'+xhr.statusText);
             }
           };
        xhr.send(body);
t0 = performance.now(); console.log("10="+t0);
} catch (err) {alert("Невозможно соединиться с сервером:\n"+err.toString()); }
     }
}

Таким образом, мы попросту оформляем процедуру обновления данных в блоках в виде функции, которую затем вызываем из обработчика – после того, как вызов сервера будет обработан. Т.е. все очень несложно. Смотрите пример - как работает вышеприведенный код (с функцией write_data).

Комментарии:
Всего комментариев:0
Пожалуйста, не забудьте ознакомиться с правилами оставления комментариев.



Подписаться на комментарии на этой странице

Мы можем выполнить

Другие услуги
Интересная и полезная
информация