17 ноября 2011 в 18:59

HTML5: Web Workers и AJAX

HTML*
Все прочнее в среду разработчиков входит HTML5. Важным его достоинством является наличие такой технологии, как web workers, которая позволяет в некоторой степени обеспечить, если не мультипоточность, то ее подобие при выполнении скриптов.

Суть технологии проста — в отдельные файлы выносятся функции, обеспечивающие функционирование AJAX, либо функции обрабатывающие большие массивы информации, которые во время работы уменьшают скорость построения страницы. Таких файлов может быть столько сколько нужно. При выполнении скрипта на стороне браузера создается специальный объект Worker, который и отвечает за вызов необходимых функций. Многие современные браузеры поддерживают данную технологию.

Теперь о том, как использовать работников.
Проверим поддержку объекта браузером:


if (!!window.Worker)
{
//технология поддерживается
}

Создается объект очень просто:

var worker = new Worker(имя файла с исполняемой функцией);

созданный нами объект обладает следующими методами:

postMessage(); //ключевой метод, инициализирующий обмен данными
onmessage(); //метод, исполняемый при поступлении ответа от вызванного работника.
onerror(); //метод, вызываемый при возникновении ошибок

Теперь сделаем сам файлик worker`а и назовем его worker.js. Внутрь поместим следующий код:

onmessage = function(ev)
{
var answ = ev.data;
};

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

var worker = new Worker('worker.js');
worker.postMessage('Hello World');

Переменная ev будет содержать объект, в свойстве data, которого будет находится то, что мы передадим в функцию, в нашем случае строка 'Hello World'. Передавать можно что угодно, в том числе сложные объекты. Теперь сделаем, чтобы наш работник, что-нибудь нам вернул. Для этого в код работника последней строкой допишем следующее:

postMessage(answ);

А в основном скрипте определим метод, который будет вызван при поступлении сообщения от работника:

worker.onmessage = function (event)
{
alert(event.data);
};


UPD:
Дополнение от demark:

Ещё надо добавить, что HTML5 позволяет создать Web Worker и без внешнего файла с помощью

blobBuilder'а:

var worker = new Worker(
    window.URL.createObjectURL(
        new BlobBuilder().append(
            "onmessage = function(e) { postMessage('hello habrahabr'); }"
        ).getBlob()
    )
);

worker.postMessage();


Вот собственно и все! Вернуть работник нам также сможет любые данные. Стоит помнить о нескольких важных моментах, таких как:
1. Работник не может получить доступ к DOM ни в каком виде. Ни одна функция работающая с DOM получающая данные о его состоянии или модифицирующая его, внутри работника недоступна. В том числе alert().
2. Работник имеет доступ к:
2.1 navigator
2.2 объект location (только чтение)
2.3 метод importScripts() (для доступа к файлам сценариев в том же домене)
2.4 объекты JavaScript, такие как Object, Array, Date, Math, String
2.5 XMLHttpRequest
2.6 методы setTimeout() и setInterval()

Так в чем же собственно мультипоточность? А в том, что объектов работников может быть сколько угодно и все они могут работать одновременно, при этом формирование страницы не будет остановлено, в ожидании пока отработает, какая-либо функция.

А теперь о важном, о том, чего нет ни в одной статье про работников: для того чтобы получить адекватный ответ при использовании технологии AJAX, необходимо отсылать СИНХРОННЫЕ запросы методом POST. Причина проста: при асинхронном запросе воркер заканчивает работу не дожидаясь ответа сервера. Логического объяснения почему не шлется методом GET я так и не нашел. Беспокоиться о том, что все встанет, не стоит, так как работник выполняется в отдельном потоке, основной скрипт будет работать дальше без остановок.

При использовании технологии воркеров для выполнения простых действий выигрыш в скорости будет минимален. Однако при сложных вычислениях можно существенно ускорить работу системы.

Приведу свою реализацию данного подхода:
1.
объект отвечающий за связь с сервером:

function  AJAXprov()
{
  var xmlhttp;
  var answServ;
  
  this.provXmlHttp = function()
  {
	  var xmlhttp;
    try 
    {
		  xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
	  } 
    catch (e) 
    {
		  try 
      {
  		  xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
  		} 
      catch (E) 
      {
		    xmlhttp = false;
		  }
	  }
	  if (!xmlhttp && typeof XMLHttpRequest!='undefined') 
    {
		  xmlhttp = new XMLHttpRequest();
	  }
    return xmlhttp;
  }

//первый - режим работы false/true (синх/асинх), 
//второй - тип запроса (POST/GET)
//третий - параметры для запроса (адрес, по которому нужно отправить запрос, 
//четвертый - необязательный аргумент, содержащий параметры для POST запроса
  this.sendAnsServ = function (modeWork, typeSend, adr, param, id, cb)
  {
    if(typeSend == 'G')
    {
      adr = adr + '?' + param;
      //alert(adr);
      httpP.open('GET', adr, modeWork);
  	 	httpP.setRequestHeader('Cache-Control', 'no-cache, must-revalidate');
  		httpP.onreadystatechange = function()
  		{
        if (httpP.readyState == 4) 
    		{
          if(httpP.status == 200) 
          {
            if(cb)
            {
              return httpP.responseText;
            }
          }
        }
      }
      httpP.send(null);
    }
    if(typeSend == 'P')
    {
      httpP.open('POST', adr, modeWork);
      httpP.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    	httpP.setRequestHeader('Cache-Control', 'post-check=0,pre-check=0, false');
    	httpP.setRequestHeader('Cache-Control',  'max-age=0, false');
    	httpP.setRequestHeader('Pragma', 'no-cache');
    	httpP.setRequestHeader('Cache-Control', 'no-cache, must-revalidate');
    	httpP.send(param);
      if(httpP.status == 200)
      {
        if(cb)
        {
          return httpP.responseText;
        }
      }
    }
  }
}

2.
Воркер

onmessage = function (obj)
{
  importScripts("/JS/classes/AjaxClass.js ");
  var ajObj = new AJAXprov();
  httpP = ajObj.provXmlHttp();
  obj = obj.data;
  answ = ajObj.sendAnsServ(objEx.mode, objEx.type, objEx.adress, objEx.parametrs, objEx.ID);

  postMessage(answ);
}

вызов:

function crWorkerAjax(param, id, cb)
{

  var workerAjax = new Worker("/JS/workers/ajaxWorker.js");
  var objEx = 
    {
      mode:false,
      type:'P',
      adress:'/router.php',
      parametrs:param,
      ID:id,
    };


  workerAjax.onmessage = function (obj)
  {
    var res = eval(obj['data']);
    cb.call(this, res, id);
  }
  workerAjax.onerror = function(err)
  {
    alert(err.message);
  }
  workerAjax.postMessage(objEx);
  
}


UPD2:
По настойчивым просьбам yui_room9 привожу его альтернативный вариант работающий в браузерах на webkit

Для основного файла:

  if (!!window.Worker){
    var worker = new Worker('worker.js');
    worker.postMessage('Hellow World');
    worker.onmessage = function (e){
      alert(e.data);
    };
  }


 Для worker:

onmessage = function(e){
  transport = new XMLHttpRequest();
  transport.open('GET', 'data.txt', true);
  transport.onreadystatechange = function(){
    if(transport.readyState == 4){
      postMessage(transport.response);
    } 
  };
  transport.send();
};


Ну а в data.txt пихаем просто текст на тест, я пихал «testing123»

Всех, кто дочитал, благодарю за внимание.
Макс @Slavenin999
карма
24,0
рейтинг 1,6
Похожие публикации
Самое читаемое Разработка

Комментарии (56)

  • +1
    Спасибо, в дополнение отмечу, что поддерживается сие чудо Safari, Chrome, Opera и Firefox. Ослик как обычно — хотя включили в Internet Explorer 10 Platform Preview 2. Web worker-ы поддерживаются Safari под iOS 5, и Android-ом начиная с 2.0
    • 0
      Firefox 3.6 не поддерживает, кстати
      • +1
        А не пробовали для FF 3.6 не устанавливать setRequestHeader? Мне помогло :)
  • 0
    что не удивительно, так как вышел он аж 21 января 2010 пруф, а вторая стадия активной разработки и внедрения html5 началась незадолго до этого в 2009 пруф
  • +3
    Не могли бы вы пояснить для новичков, почему надо отправлять синхронные запросы методом POST? Что значит «адекватный ответ»?
    • 0
      суть в том, что воркер может завершится ранее, чем будет получен ответ и если в качестве обработчика будет стоять ф-ция из воркера, есть вариант не предсказуемого поведения браузера, посколько колбек ф-ция может уже быть недоступна
      • 0
        Ещё один вопрос от новичков: как функия воркера может быть недоступна? Если в ней был создан колбек, который используется асинхронным запросом, разве такую функцию может удалить gc? Или есть какие-то особенности про обработке воркеров?
        • 0
          Я не особо гуру в механизмах JS, но в приведенном мной примере колбэк определен не в воркере, а в объекте аякса, к тому моменту, как объект возвращает значение его уже некуда возвращать. Асинхронный запрос подразумевает, что скрипт не останавливаясь выполняется дальше, а дальше после отсыла запроса, идет возвращение значения workerAjax.postMessage(objEx); и воркер удаляется(наверно). Поэтому колбэк определен в основном потоке, а не в воркере, в который нужно будет импортировать еще и скрипт, в котором содержится вызываемая колбэк функция. Наверно, как-то так. :)
  • +3
    Мне кажется использование workers для AJAX вызовов это неадекватное их использование, т.к. в этом случае асинхронность обеспечивает сам ActiveX объект, который делает HTTP запросы. А смысл появляется только в случае ресурсоемкого JavaScript кода, т.к. весь JavaScript в браузере выполняется в одном потоке. Если бы до появления workers все AJAX запросы были синхронными, то большей частью современных веб-приложений невозможно было бы пользоваться.
  • +1
    А теперь о важном, о том, чего нет ни в одной статье про работников: для того чтобы получить адекватный ответ при использовании технологии AJAX, необходимо отсылать СИНХРОННЫЕ запросы методом POST.

    извините, не верю
    • –1
      а вы попробуйте, синхронные и асинхронные варианты в разных браузерах
  • +4
    if (!!window.Worker)

    Интересная конструкция…
    • 0
      Стандартная ситуация — когда нужно проверить на существование.
      ! — «не», просто преобразует в булевый тип

      !!- вернет булевый тип, и реальное состояние дел. (если там что-то было: Объект, массив, строка — true;
      null, undefined, false — false)

      • +1
        если worker'а нет, то if (window.Worker) вернет false. Если есть, то вернет true. Не вижу проблемы
        • 0
          Да, но это принудительное приведения значения к булю. Т.к в оригинале есть window.Worker есть это будет функция, если нету то undefined, это не более чем хорошая практика.
  • +3
    if (!!window.Worker)
    Эта пять.

    Ещё надо добавить, что HTML5 позволяет создать Web Worker и без внешнего файла с помощью blobBuilder'а:

    var worker = new Worker(
    	window.URL.createObjectURL(
    		new BlobBuilder().append(
    			"onmessage = function(e) { postMessage('hello habrahabr'); }"
    		).getBlob()
    	)
    );
    
    worker.postMessage();

    Подробнее на HTML5Rocks
    • 0
      Ну тогда уж и ~~ запишите в коллекцию )

      Math.floor(5.6) == ~~5.6
  • –3
    Насчёт синхронности/асинхронности — разницы нет. Потому что все события работают мгновенно и быстро. То есть, если у вас синхронный запрос, все события просто навсего обрабатываются внутри *request, а если асинхронный, то вам это нужно делать самому — подписываться. Но в этом нет ничего тормозного — работать будет также быстро, как и синхронный.
    • 0
      Ну конечно нет, а то что синхронный запрос подвешивает браузер до завершения запроса это такие мелочи
      • –2
        Вероятно, вы не поняли. Я тут хотел выразить мысль о том, о чём пишет автор — он пишет, «что нужно применять именно синхронный запрос». И уточняет, что из-за скорости.

        Я это опровергаю, говоря то, что асинхронный будет так же быстр (то есть, без разницы).

        То есть, я именно за асинхронность. Синхронность устарела и не применяется уже почти нигде.
        • 0
          Посмотрите внимательней,

          Автор пишет про синхронный запрос не из за скорости, а из за бага насколько я понимаю.
          Скорость будет за счёт того что сами воркеры асинхронные и работают в параллели.
          • 0
            То есть, баг в том, что процесс Worker выйдет после выполнения работы, не дождавшись ответа асинхронного запроса? Или в чём именно баг?
            • 0
              >>Для того чтобы получить адекватный ответ при использовании технологии AJAX, необходимо отсылать СИНХРОННЫЕ запросы методом POST

              То есть любой другой вариант просто будет не рабочий по словам автора, хотя я вот в хроме налабал пример, ещё чутка потестирую и в основной поток коммент с примером выложу.
        • –1
          нет, не из-за скорости, где это я такое написал? смысл в том, что при асинхронном запросе воркер возвращает результат не дождавшись ответа с сервера.
          • +1
            При использовании данной технологии для выполнения простых действий выигрыш в скорости будет минимален. Однако при сложных вычислениях можно существенно ускорить работу системы.
            • 0
              это написано про воркеры в целом, а не про аякс
              • 0
                Это написано в том же обзаце, что и про POST-баг-синнхронность-асинхронность, поэтому было сразу видно, что это как-бы уточнение по поводу этой темы.

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

                Вот сейчас всё становится ясно, спасибо.

                — А насчёт скорости работы Worker для системы в целом — всё правильно, согласен. Спасибо за перевод, читал по англ. полгода назад про эти вокеры, удобная вещь, как-бы независимые ветки (Thread).
                • 0
                  Это не перевод, согласен, прежде чем писать статью, я прочитал курс на intuit`e по html5, но больше статей не изучал, все что написано — добыто своими силами
            • +1
              Действительно, имела место речевая ошибка. Немного подправил. Надеюсь, больше не будет возникать двусмысленности в написанном.
              • 0
                Ура.
  • –1
    Автор обновите пост, в webkit браузерах работает

    Для основного файла:
      if (!!window.Worker){
        var worker = new Worker('worker.js');
        worker.postMessage('Hellow World');
        worker.onmessage = function (e){
          alert(e.data);
        };
      }
    


    Для worker:

    onmessage = function(e){
      transport = new XMLHttpRequest();
      transport.open('GET', 'data.txt', true);
      transport.onreadystatechange = function(){
        if(transport.readyState == 4){
          postMessage(transport.response);
        } 
      };
      transport.send();
    };
    


    Ну а в data.txt пихаем просто текст на тест, я пихал «testing123»
    • –1
      а мой код работает во всех браузерах, поэтому смысла в нем что-то менять пока не вижу
      • 0
        Автор вы пишете статью или балаган устраиваете?

        Поясняю, вы писали

        1) А теперь о важном, о том, чего нет ни в одной статье про работников: для того чтобы получить адекватный ответ при использовании технологии AJAX, необходимо отсылать СИНХРОННЫЕ запросы методом POST.


        Я код выше написал, который опровергает ваши слова, я же не сказал что он работает везде, я уточнил сразу что только в webkite, в остальных либо начнёт работать со временем либо не начнёт, о нормальном варианте людям знать не мешает.

        2) Причина проста: при асинхронном запросе воркер заканчивает работу не дожидаясь ответа сервера. Логического объяснения почему не шлется методом GET я так и не нашел.


        Опять же мой код эти слова опроверг, и про GET и про заканчивает работу.
        • –2
          нормальный вариант — это тот, который работает везде. Это как свежесть — она только одна ©
          • –1
            да, и стоит наверно все-таки проверять статус ответа сервера, а то state может быть 4, а вот status 404…
            • –1
              Это пример, исключительно для данного бага, который в вэбките не подтвердился.
              Накой чёрт ему писать нормальный callback и прочие обвязки?
              Чтобы вам некчему придратся было?

              Ну уж извините, я его писал с другой целью.
          • –1
            Нормальный вариант это чистый вариант, о котором я и прошу сделать обновление, причём дополнение а не похерить пост к чертям.
            Научитесь воспринимать критику нормально.

            От того что в 6м IE в давние времена все и всё делали через зад, никто не называл это нормальным вариантом, это называлось костылями.
            • –1
              уважаемый yui_room9, приведенный мной пример объекта для обмена аякс запросами не содержит костылей, а реализует одну из самых распространенных кроссбраузерных функций создания объекта XMLHttpRequest. Как только все браузеры начнут поддерживать унифицированную технологию создания объектов, я буду первым, кто перепишет свой класс и упростит его. Но пока, к моему величайшему сожалению, это не так, посему я, подчеркиваю еще раз — я, реализовал обмен данными именно таким образом. Если вас он не устраивает я на нем не настаиваю, делайте как хотите. Для меня главное, что он работает во многих современных браузерах. Кстати, тот объект который есть переводится с веб воркеров на обычный код, фунциклирующий в IE, буквально несколькими строками.
              • 0
                Я ничего не имею против вашего лично метода и вас лично.

                Я просто попросил вас сделать дополнение к фразе

                для того чтобы получить адекватный ответ при использовании технологии AJAX, необходимо отсылать СИНХРОННЫЕ запросы методом POST


                О том что webkit браузеры поддерживают асинхронный запрос в воркере.
                Что странного в этой просьбе?
                Вы же делитесь информацией с людьми, зачем же часть информации утаивать?
                • –1
                  обновил.
                  • 0
                    Спасибо
  • 0
    Да, не очень внятно описано, почему именно нельзя использовать асинхронный запрос. Однако статья в целом полезная.
    • 0
      дополнил статью, надеюсь, получилось понятно
  • +1
    Не сочтите за придирчивость, просто в глаза бросилась опечатка – «Hellow» пишется без «w». =)
    • –1
      исправлено :)
  • +1
    Странно, у меня работает асинхронный GET в воркере и ответ возвращается в firefox 8, что я делаю не так?
    • +1
      Пример от yui_room9 тоже отлично работает, только postMessage(transport.responseText);
      • 0
        Вот :) +1 Браузер, я в FF 8 не проверял.
        Всёже это глюк а не нормальное поведение с синхронным запросом.
    • –1
      опера 11.52 chrome 15.0.874.121 m возвращают 'undefined', обновил ff до 8 версии, он тупо вылетает на строке создания воркера
      var workerAjax = new Worker('/JS/workers/ajaxWorker.js');
      при этом не выдает никакой ошибки… О_О
      • –1
        нашел консоль в этом мегобраузере… пишет
        Ошибка: Could not get domain!
        Источник: localhost/JS/workers/mainWorkers.js
        Строка: 39

        если адрес скопировать то файл нормально открывается, да и другие браузеры нормально отрабатывают, один фф чего-то артачится…
        • –1
          как оказалось, касяк в самой мазиле подробнее здесь
  • 0
    А ещё можно сказать, что воркеры не просто позволяют передавать любые объекты, а ещё их клонируют. Поэтому если передавать большой объём данных между потоками, то будет заметное блокирование GUI браузера в момент приёма данных от воркера.
    • 0
      У меня есть скрипт ~20мб, в котором просто декларирование трёх больших массивов со строками и числами. Если его подключать просто через создание тега script, то исполнение кода занимает около 3 сек с блокированием на это время GUI. Если делать через воркер: запустить воркер, внутри вызвать importScripts() и выдать «наружу» только данные, то время до готовности увеличилось вдвое — 7-8 сек, но при этом GUI блокируется только в самом конце на 1-2 сек, когда приходит ответ от воркера. Так что в моей ситуации воркеры практически не помогли.

      P.S.
      Тестировал в Chrome 15 с файловой системы (вообще без веб-сервера), так что временем загрузки скрипта можно пренебречь.
      20мб — это большой и довольно редкий случай, для мелких объёмов время до готовности тоже удваивается, а критичного времени блокирования GUI для них нет — поэтому для них ситуация даже ухудшилась.
    • 0
      Чтобы не клонировать большие данные, можно использовать ArrayBuffer
      http://stackoverflow.com/questions/33254303/how-fast-are-web-workers-messages/33309300#33309300
  • 0
    Функцию provXmlHttp можно улучшить, если в первую очередь проверить наличие XMLHttpRequest объекта и использовать его, если возможно. И только, если он не поддерживается, тогда использовать ActiveX. Типа как сначала проверяем предпочтительный вариант, и если не работает, то переходим к fallback решению.
  • 0
    … Таких файлов может быть столько сколько нужно... (Из поста)
    Хочется уточнить, что 256 — максимальное количество работников. Потом просто стёк переполняется и пишет ошибку:
    Maximum number of Web Worker instances(256) exceeded for this window.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.