JavaScript
Асинхронность

Михаил Давыдов
Разработчик JavaScript
Задача




    •  Качаем 1 файл
    •  После отправляем данные на 2 сервера
    •  Синхронизируемся




3
Синхронный код
    Сделаем обертку над XMLHttpRequest


     function syncXHR(method, url, data) {
         var xhr = new XMLHttpRequest();

         xhr.open(method, url, false);
         xhr.send(data);

         return xhr.responseText;
     }


     var data = syncXHR('GET', 'http://host1/page.json');
     data = processData(data);

     syncXHR('POST', 'http://host2/result/', data);
     syncXHR('POST', 'http://host3/result/', data);
     alert('Done!');


4
Схема загрузки


    Подготовка            Обработка                Отправка                Алерт


             Блокировка               Блокировка              Блокировка




                 Запрос                 Запрос                  Запрос




5
                                                                           время
Асинхронный код
    Сделаем обертку над XMLHttpRequest


     function asyncXHR(method, url, data, callback) {
         var xhr = new XMLHttpRequest();

         xhr.open(method, url, true);

         xhr.onreadystatechange = function () {
             if (xhr.readyState === 4) {
                 if (xhr.status === 200) {
                     callback(null, xhr.responseText);
                 } else {
                     callback(‘error’);
                 }
             }
         }

         xhr.send(data);
     }

6
Асинхронный код
    Сам код. Изменилось все.

     asyncXHR ('GET', ‘http://host1/page.json’, null,
     function (err, data) {
         data = processData(data);
         var counter = 2;

            function done(err, data) {
                counter--;
                if (!counter) alert(‘Done!’);
            }

           asyncXHR(‘POST’, ‘http://host2/result/’, data, done);
           asyncXHR(‘POST’, ‘http://host3/result/’, data, done);
     });




7
Схема загрузки



      Подготовка             Обработка Отправка              Алерт

                   Ожидание                       Ожидание




                    Запрос




8
                                                                     время
Где применяется "асинхронность"


    •  Производительность
    •  Интерфейс пользователя
    •  Проблемы
     –  Много лишнего шума
     –  Проблема синхронизации
     –  Куча вложенных колбэков: Pyramid of Doom

    •  Несколько реализаций
     –  Event Loop




9
Основа – Event Loop


     •  Основа всех событийных систем
     •  Использует очередь событий
     •  Ждет события
     •  Выполняет события из очереди
      –  События в очередь поступают во время выполнения событий
      –  События генерируют события

     •  Завершается когда очередь пуста


10
Паттерны

Callback,
Event,
Promise,
Deferred
Callback
     Типичный пример – обертка над XMLHttpRequest


      function asyncXHR(method, url, data, callback) {
          var xhr = new XMLHttpRequest();

           xhr.open(method, url, true);

           xhr.onreadystatechange = function () {
               if (xhr.readyState === 4) {
                   if (xhr.status === 200) {
                       callback(null, xhr.responseText);
                   } else {
                       callback(‘error’);
                   }
               }
           }

           xhr.send(data);
      }

12
Callback




     •  Самый простой вариант
      –  Дешевая абстракция

     •  В него могут приходить ошибки и данные
      –  cтиль node.js
      –  callback(err, data)




13
Event: EventEmitter, PubSub
     Общая схема


      function EventEmitter () {
          this.events = {};
      }

      EventEmitter.prototype = {
          on: function (event, callback) {},
          off: function (event, callback) {},
          emit: function (event, data) {}
      };




14
                                   http://nodejs.org/api/events.html
Event
     Типичный пример – обертка над XMLHttpRequest


      function eventXHR(method, url, data) {
          var xhr = new XMLHttpRequest(),
              event = new EventEmitter();

           xhr.open(method, url, true);
           xhr.onreadystatechange = function () {
               if (xhr.readyState === 4) {
                   if (xhr.status === 200) {
                       event.emit(‘data’, xhr.responseText);
                   } else {
                       event.emit(‘error’);
                   }
               }
           }
           xhr.send(data);
           return event;
      }

15
Event
     Сам код. Изменилось не так много.

      eventXHR('GET', ‘http://host1/page.json’)
      .on(‘data’, function (data) {
          data = processData(data);
          var counter = 2;

           function done() {
               counter--;
               if (!counter) alert(‘Done!’);
           }

          eventXHR(‘POST’, ‘http://host2/result/’, data)
              .on(‘data’, done);
          eventXHR(‘POST’, ‘http://host3/result/’, data)
              .on(‘data’, done);
      })
      .on(‘error’, function(){ });



16
Event



     •  Абстракция более высокого уровня
     •  Ошибки отделены от данных
      –  Возможны логически разные типы данных

     •  Можно отписаться от события
     •  Можно подписаться несколько раз
     •  Можно передавать как аргумент


17
Promise


     •  Это Обещанные данные
     •  Имеет 3 состояния
      –  Не выполнен (выполняется)
      –  Выполнен (результат)
      –  Отклонен (ошибка)

     •  Меняет состояние только 1 раз
      –  В событиях состояние меняется сколько угодно раз

     •  Запоминает свое состояние
      –  В отличии от события в котором состояние – это поток



18
                  http://wiki.commonjs.org/wiki/Promises
Promise
     Общая схема


      function Promise () {
          this.isFulfilled = false;
          this.isRejected = false;
          this.isResolved = false;
          this.result = null;
      }

      Promise.prototype = {
          then: function (fulfilled, rejected, progressed) {},
          reject: function (error) {},
          resolve: function (data) {}
      };




19
Promise
     Типичный пример – обертка над XMLHttpRequest


      function promiseXHR(method, url, data) {
          var xhr = new XMLHttpRequest(),
              promise = new Promise();

           xhr.open(method, url, true);
           xhr.onreadystatechange = function () {
               if (xhr.readyState === 4) {
                   if (xhr.status === 200) {
                       promise.resolve(xhr.responseText);
                   } else {
                       promise.reject(‘Error ’ + xhr.status);
                   }
               }
           }
           xhr.send(data);
           return promise;
      }

20
Promise
     Сам код

      promiseXHR('GET', ‘http://host1/page.json’)
      .then(function (data) {
          data = processData(data);
          var promises = [
              promiseXHR(‘POST’, ‘http://host2/result/’, data),
              promiseXHR(‘POST’, ‘http://host3/result/’, data)
          ];

            when(promises, function (data) {
                alert(‘Done!’);
            });
      });




21
Promise



     •  Запоминает свое состояние
     •  Всегда возвращает один результат
      –  В отличие от события где данные – поток
      –  Не зависит от времени опроса

     •  Можно передавать как аргумент
     •  Можно выполнять операции
      –  then




22
Deferred




     •  Это защищенный Promise
     •  Разграничивает слушателя и Promise
     •  Слушатель не может вмешаться
      –  С чистыми промисами можно завершить промис на слушателе
      –  Меньше логических ошибок




23
        http://api.jquery.com/category/deferred-object/
Deferred
     Общая схема


      function Deferred () {
          this._promise = {
              then: function (fulfilled, rejected, progressed) {}
          };
      }

      Deferred.prototype = {
          promise: function (error) {
              return this._promise;
          },
          reject: function (error) {},
          resolve: function (data) {}
      };




24
Deferred
     Типичный пример – обертка над XMLHttpRequest


      function defferedXHR(method, url, data) {
          var xhr = new XMLHttpRequest(),
              deferred = new Deffered();

           xhr.open(method, url, true);
           xhr.onreadystatechange = function () {
               if (xhr.readyState === 4) {
                   if (xhr.status === 200) {
                       deferred.resolve(xhr.responseText);
                   } else {
                       deferred.reject(‘Error ’ + xhr.status);
                   }
               }
           }
           xhr.send(data);
           return deferred.promise();
      }

25
Deferred
     Сам код

      defferedXHR('GET', ‘http://host1/page.json’)
      .then(function (data) {
          data = processData(data);
          var promises = [
              defferedXHR(‘POST’, ‘http://host2/result/’, data),
              defferedXHR(‘POST’, ‘http://host3/result/’, data)
          ];

           when(promises, function (data) {
               alert(‘Done!’);
           });
      })
      .reject(‘Mua-ha-ha!’); // Это сделать нельзя




26
Библиотеки

Streamlinejs,
Fibers
Step,
Q
Streamline
     Streamline – попытка избавится от асинхронного шума
     Используют callback(err, data)




      var data = asyncXHR('GET', '/', null, _);
      asyncXHR('POST', '/', data, _);
      asyncXHR('POST', '/', data, _);
      alert('Done!');




28
                              https://github.com/Sage/streamlinejs
Streamline – результат генерации
     Happy Debug!

      (function main(_) {
        var data;
        var __frame = {
           name: "main”,
           line: 1
        };

        return __func(_, this, arguments, main, 0, __frame, function __$main() {
          return asyncXHR("GET", "/", null, __cb(_, __frame, 17, 11, function ___(__0, __1) {
            data = __1;
            return asyncXHR("POST", "/", data, __cb(_, __frame, 18, 0, function __$main() {
              return asyncXHR("POST", "/", data, __cb(_, __frame, 19, 0, function __$main() {
                alert("Done!");
                _();
              }, true));
            }, true));
          }, true));
        });
      }).call(this, __trap);




29
Streamlinejs



     •  Генерация кода – результат ужасен!
     •  Шум из массы _
     •  Его цель – выполнять асинхронный код
        последовательно




30
Fibers
     Fibers – попытка избавится от асинхронного шума
     Используют callback(err, data)

      var Future = require('fibers/future'),
          wait = Future.wait;

      var asyncXHR = Future.wrap(asyncXHR);

      Fiber(function () {
          var data = asyncXHR(‘GET’, '...’, null).wait();
          data = processData(data);

          asyncXHR(‘POST’, '...’, data).wait();
          asyncXHR(‘POST’, '...’, data).wait();
          alert(‘Done!’);
      }).run();



                          https://github.com/laverdet/node-fibers	

31                           https://github.com/0ctave/node-sync
Fibers


     •  Особая версия Node.js
      –  Хак механизма yield()

     •  Похожи на треды
      –  Не могут прерываться где угодно процессором
      –  Меньше расходов на «безопасные зоны»

     •  Похожи на Event Loop
      –  yield() и ручное прерывание фибера
      –  Блокировка остальных фиберов
      –  Нет реального параллелизма (не занимают все ядра процессора)

     •  Параллельные запросы последовательно
      –  Необходимо использовать дополнительные функции


32
Step
     Позволяет выполнять асинхронный код в синхронном стиле
     Работает с callback(err, data)

      Step(
          function () {
              asyncXHR('GET’, ‘...’, null, this);
          },
          function (err, data) {
              return processData(data);
          },
          function (err, data) {
              asyncXHR(‘POST’, ‘...’, data, this.parallel());
              asyncXHR(‘POST’, ‘...’, data, this.parallel());
          },
          function (err, result1, result2) {
              alert(‘Done!’);
          }
      );


33
                                https://github.com/creationix/step
Q
     Работает с Promise
     Представляет интерфейс для работы с промисами

      var data = promiseXHR('GET', '...');

      data.than(processAndSendData).than(function () {
          alert(‘Done!’);
      });

      function processAndSendData(data) {
          data = processData(data);
          return sendData(data);
      }

      function sendData(data) {
          return Q.all([
              promiseXHR(‘POST’, ‘...’, data),
              promiseXHR(‘POST’, ‘...’, data)
          ]);
      }

34
                                    https://github.com/kriskowal/q
Асинхронность

     •  Событийный ввод-вывод
     •  Работа с GUI
     •  Паттерны
      –  Callback
      –  EventEmitter
      –  Promise
      –  Deferred

     •  Хаки
      –  Streamline
      –  Fibers

     •  Библиотеки
      –  Step
      –  Q
35
Михаил Давыдов

     Разработчик JavaScript




     azproduction@yandex-team.ru
     azproduction




Спасибо

Михаил Давыдов - JavaScript. Асинхронность

  • 2.
  • 3.
    Задача •  Качаем 1 файл •  После отправляем данные на 2 сервера •  Синхронизируемся 3
  • 4.
    Синхронный код Сделаем обертку над XMLHttpRequest function syncXHR(method, url, data) { var xhr = new XMLHttpRequest(); xhr.open(method, url, false); xhr.send(data); return xhr.responseText; } var data = syncXHR('GET', 'http://host1/page.json'); data = processData(data); syncXHR('POST', 'http://host2/result/', data); syncXHR('POST', 'http://host3/result/', data); alert('Done!'); 4
  • 5.
    Схема загрузки Подготовка Обработка Отправка Алерт Блокировка Блокировка Блокировка Запрос Запрос Запрос 5 время
  • 6.
    Асинхронный код Сделаем обертку над XMLHttpRequest function asyncXHR(method, url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.responseText); } else { callback(‘error’); } } } xhr.send(data); } 6
  • 7.
    Асинхронный код Сам код. Изменилось все. asyncXHR ('GET', ‘http://host1/page.json’, null, function (err, data) { data = processData(data); var counter = 2; function done(err, data) { counter--; if (!counter) alert(‘Done!’); } asyncXHR(‘POST’, ‘http://host2/result/’, data, done); asyncXHR(‘POST’, ‘http://host3/result/’, data, done); }); 7
  • 8.
    Схема загрузки Подготовка Обработка Отправка Алерт Ожидание Ожидание Запрос 8 время
  • 9.
    Где применяется "асинхронность" •  Производительность •  Интерфейс пользователя •  Проблемы –  Много лишнего шума –  Проблема синхронизации –  Куча вложенных колбэков: Pyramid of Doom •  Несколько реализаций –  Event Loop 9
  • 10.
    Основа – EventLoop •  Основа всех событийных систем •  Использует очередь событий •  Ждет события •  Выполняет события из очереди –  События в очередь поступают во время выполнения событий –  События генерируют события •  Завершается когда очередь пуста 10
  • 11.
  • 12.
    Callback Типичный пример – обертка над XMLHttpRequest function asyncXHR(method, url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.responseText); } else { callback(‘error’); } } } xhr.send(data); } 12
  • 13.
    Callback •  Самый простой вариант –  Дешевая абстракция •  В него могут приходить ошибки и данные –  cтиль node.js –  callback(err, data) 13
  • 14.
    Event: EventEmitter, PubSub Общая схема function EventEmitter () { this.events = {}; } EventEmitter.prototype = { on: function (event, callback) {}, off: function (event, callback) {}, emit: function (event, data) {} }; 14 http://nodejs.org/api/events.html
  • 15.
    Event Типичный пример – обертка над XMLHttpRequest function eventXHR(method, url, data) { var xhr = new XMLHttpRequest(), event = new EventEmitter(); xhr.open(method, url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { event.emit(‘data’, xhr.responseText); } else { event.emit(‘error’); } } } xhr.send(data); return event; } 15
  • 16.
    Event Сам код. Изменилось не так много. eventXHR('GET', ‘http://host1/page.json’) .on(‘data’, function (data) { data = processData(data); var counter = 2; function done() { counter--; if (!counter) alert(‘Done!’); } eventXHR(‘POST’, ‘http://host2/result/’, data) .on(‘data’, done); eventXHR(‘POST’, ‘http://host3/result/’, data) .on(‘data’, done); }) .on(‘error’, function(){ }); 16
  • 17.
    Event •  Абстракция более высокого уровня •  Ошибки отделены от данных –  Возможны логически разные типы данных •  Можно отписаться от события •  Можно подписаться несколько раз •  Можно передавать как аргумент 17
  • 18.
    Promise •  Это Обещанные данные •  Имеет 3 состояния –  Не выполнен (выполняется) –  Выполнен (результат) –  Отклонен (ошибка) •  Меняет состояние только 1 раз –  В событиях состояние меняется сколько угодно раз •  Запоминает свое состояние –  В отличии от события в котором состояние – это поток 18 http://wiki.commonjs.org/wiki/Promises
  • 19.
    Promise Общая схема function Promise () { this.isFulfilled = false; this.isRejected = false; this.isResolved = false; this.result = null; } Promise.prototype = { then: function (fulfilled, rejected, progressed) {}, reject: function (error) {}, resolve: function (data) {} }; 19
  • 20.
    Promise Типичный пример – обертка над XMLHttpRequest function promiseXHR(method, url, data) { var xhr = new XMLHttpRequest(), promise = new Promise(); xhr.open(method, url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { promise.resolve(xhr.responseText); } else { promise.reject(‘Error ’ + xhr.status); } } } xhr.send(data); return promise; } 20
  • 21.
    Promise Сам код promiseXHR('GET', ‘http://host1/page.json’) .then(function (data) { data = processData(data); var promises = [ promiseXHR(‘POST’, ‘http://host2/result/’, data), promiseXHR(‘POST’, ‘http://host3/result/’, data) ]; when(promises, function (data) { alert(‘Done!’); }); }); 21
  • 22.
    Promise •  Запоминает свое состояние •  Всегда возвращает один результат –  В отличие от события где данные – поток –  Не зависит от времени опроса •  Можно передавать как аргумент •  Можно выполнять операции –  then 22
  • 23.
    Deferred •  Это защищенный Promise •  Разграничивает слушателя и Promise •  Слушатель не может вмешаться –  С чистыми промисами можно завершить промис на слушателе –  Меньше логических ошибок 23 http://api.jquery.com/category/deferred-object/
  • 24.
    Deferred Общая схема function Deferred () { this._promise = { then: function (fulfilled, rejected, progressed) {} }; } Deferred.prototype = { promise: function (error) { return this._promise; }, reject: function (error) {}, resolve: function (data) {} }; 24
  • 25.
    Deferred Типичный пример – обертка над XMLHttpRequest function defferedXHR(method, url, data) { var xhr = new XMLHttpRequest(), deferred = new Deffered(); xhr.open(method, url, true); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { deferred.resolve(xhr.responseText); } else { deferred.reject(‘Error ’ + xhr.status); } } } xhr.send(data); return deferred.promise(); } 25
  • 26.
    Deferred Сам код defferedXHR('GET', ‘http://host1/page.json’) .then(function (data) { data = processData(data); var promises = [ defferedXHR(‘POST’, ‘http://host2/result/’, data), defferedXHR(‘POST’, ‘http://host3/result/’, data) ]; when(promises, function (data) { alert(‘Done!’); }); }) .reject(‘Mua-ha-ha!’); // Это сделать нельзя 26
  • 27.
  • 28.
    Streamline Streamline – попытка избавится от асинхронного шума Используют callback(err, data) var data = asyncXHR('GET', '/', null, _); asyncXHR('POST', '/', data, _); asyncXHR('POST', '/', data, _); alert('Done!'); 28 https://github.com/Sage/streamlinejs
  • 29.
    Streamline – результатгенерации Happy Debug! (function main(_) { var data; var __frame = { name: "main”, line: 1 }; return __func(_, this, arguments, main, 0, __frame, function __$main() { return asyncXHR("GET", "/", null, __cb(_, __frame, 17, 11, function ___(__0, __1) { data = __1; return asyncXHR("POST", "/", data, __cb(_, __frame, 18, 0, function __$main() { return asyncXHR("POST", "/", data, __cb(_, __frame, 19, 0, function __$main() { alert("Done!"); _(); }, true)); }, true)); }, true)); }); }).call(this, __trap); 29
  • 30.
    Streamlinejs •  Генерация кода – результат ужасен! •  Шум из массы _ •  Его цель – выполнять асинхронный код последовательно 30
  • 31.
    Fibers Fibers – попытка избавится от асинхронного шума Используют callback(err, data) var Future = require('fibers/future'), wait = Future.wait; var asyncXHR = Future.wrap(asyncXHR); Fiber(function () { var data = asyncXHR(‘GET’, '...’, null).wait(); data = processData(data); asyncXHR(‘POST’, '...’, data).wait(); asyncXHR(‘POST’, '...’, data).wait(); alert(‘Done!’); }).run(); https://github.com/laverdet/node-fibers 31 https://github.com/0ctave/node-sync
  • 32.
    Fibers •  Особая версия Node.js –  Хак механизма yield() •  Похожи на треды –  Не могут прерываться где угодно процессором –  Меньше расходов на «безопасные зоны» •  Похожи на Event Loop –  yield() и ручное прерывание фибера –  Блокировка остальных фиберов –  Нет реального параллелизма (не занимают все ядра процессора) •  Параллельные запросы последовательно –  Необходимо использовать дополнительные функции 32
  • 33.
    Step Позволяет выполнять асинхронный код в синхронном стиле Работает с callback(err, data) Step( function () { asyncXHR('GET’, ‘...’, null, this); }, function (err, data) { return processData(data); }, function (err, data) { asyncXHR(‘POST’, ‘...’, data, this.parallel()); asyncXHR(‘POST’, ‘...’, data, this.parallel()); }, function (err, result1, result2) { alert(‘Done!’); } ); 33 https://github.com/creationix/step
  • 34.
    Q Работает с Promise Представляет интерфейс для работы с промисами var data = promiseXHR('GET', '...'); data.than(processAndSendData).than(function () { alert(‘Done!’); }); function processAndSendData(data) { data = processData(data); return sendData(data); } function sendData(data) { return Q.all([ promiseXHR(‘POST’, ‘...’, data), promiseXHR(‘POST’, ‘...’, data) ]); } 34 https://github.com/kriskowal/q
  • 35.
    Асинхронность •  Событийный ввод-вывод •  Работа с GUI •  Паттерны –  Callback –  EventEmitter –  Promise –  Deferred •  Хаки –  Streamline –  Fibers •  Библиотеки –  Step –  Q 35
  • 36.
    Михаил Давыдов Разработчик JavaScript azproduction@yandex-team.ru azproduction Спасибо