Михаил Давыдов
Разработчик JavaScript
JavaScript
Асинхронность
3
Задача
•  Качаем 1 файл
•  После отправляем данные на 2 сервера
•  Синхронизируемся
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!’);
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);
}
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);
});
8
Схема загрузки
время
Ожидание Ожидание
Запрос
Подготовка Обработка Отправка Алерт
9
Асинхронность
•  Производительность
–  Код выше
•  Интерфейс пользователя
•  Проблемы
–  Много лишнего шума
–  Проблема синхронизации
–  Куча вложенных колбэков: Pyramid of Doom
•  Несколько реализаций
–  Event Loop
10
Event Loop
•  Основа всех событийных систем
•  Использует очередь событий
•  Ждет события
•  Выполняет события из очереди
–  События в очередь поступают во время выполнения событий
–  События генерируют события
•  Завершается когда очередь пуста
Паттерны
Callback,
Event,
Promise,
Deferred
12
Типичный пример – обертка над XMLHttpRequest
Callback
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);
}
13
Callback
•  Самый простой вариант
–  Дешевая абстракция
•  В него могут приходить ошибки и данные
–  cтиль node.js
–  callback(err, data)
14
Общая схема
Event: EventEmitter, PubSub
function EventEmitter () {
this.events = {};
}
EventEmitter.prototype = {
on: function (event, callback) {},
off: function (event, callback) {},
emit: function (event, data) {}
};
http://nodejs.org/api/events.html
15
Типичный пример – обертка над XMLHttpRequest
Event
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;
}
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(){ });
17
Event
•  Абстракция более высокого уровня
•  Ошибки отделены от данных
–  Возможны логически разные типы данных
•  Можно отписаться от события
•  Можно подписаться несколько раз
•  Можно передавать как аргумент
18
Promise
•  Это Обещанные данные
•  Имеет 3 состояния
–  Не выполнен (выполняется)
–  Выполнен (результат)
–  Отклонен (ошибка)
•  Меняет состояние только 1 раз
–  В событиях состояние меняется сколько угодно раз
•  Запоминает свое состояние
–  В отличии от события в котором состояние – это поток
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) {}
};
20
Типичный пример – обертка над XMLHttpRequest
Promise
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;
}
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!’);
});
});
22
Promise
•  Запоминает свое состояние
•  Всегда возвращает один результат
–  В отличие от события где данные – поток
–  Не зависит от времени опроса
•  Можно передавать как аргумент
•  Можно выполнять операции
–  then
23
Deferred
•  Это защищенный Promise
•  Разграничивает слушателя и Promise
•  Слушатель не может вмешаться
–  С чистыми промисами можно завершить промис на слушателе
–  Меньше логических ошибок
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) {}
};
25
Типичный пример – обертка над XMLHttpRequest
Deferred
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();
}
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!’); // Это сделать нельзя
Библиотеки
Streamlinejs,
Fibers
Step,
Q
28
Streamline – попытка избавится от асинхронного шума
Используют callback(err, data)
Streamline
var data = asyncXHR('GET', '/', null, _);
asyncXHR('POST', '/', data, _);
asyncXHR('POST', '/', data, _);
alert('Done!');
https://github.com/Sage/streamlinejs
29
Happy Debug!
Streamline – результат генерации
(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);
30
Streamlinejs
•  Генерация кода – результат ужасен!
•  Шум из массы _
•  Его цель – выполнять асинхронный код
последовательно
31
Fibers – попытка избавится от асинхронного шума
Используют callback(err, data)
Fibers
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	

https://github.com/0ctave/node-sync
32
Fibers
•  Особая версия Node.js
–  Хак механизма yield()
•  Похожи на треды
–  Не могут прерываться где угодно процессором
–  Меньше расходов на «безопасные зоны»
•  Похожи на Event Loop
–  yield() и ручное прерывание фибера
–  Блокировка остальных фиберов
–  Нет реального параллелизма (не занимают все ядра процессора)
•  Параллельные запросы последовательно
–  Необходимо использовать дополнительные функции
33
Позволяет выполнять асинхронный код в синхронном стиле
Работает с callback(err, data)
Step
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!’);
}
);
https://github.com/creationix/step
34
Работает с Promise
Представляет интерфейс для работы с промисами
Q
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)
]);
}
https://github.com/kriskowal/q
35
Асинхронность
•  Событийный ввод-вывод
•  Работа с GUI
•  Паттерны
–  Callback
–  EventEmitter
–  Promise
–  Deferred
•  Хаки
–  Streamline
–  Fibers
•  Библиотеки
–  Step
–  Q
Михаил Давыдов
Разработчик JavaScript
azproduction@yandex-team.ru
azproduction
Спасибо

JavaScript. Async (in Russian)

  • 2.
  • 3.
    3 Задача •  Качаем 1файл •  После отправляем данные на 2 сервера •  Синхронизируемся
  • 4.
    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!’);
  • 5.
    5 Схема загрузки время БлокировкаБлокировка Блокировка ЗапросЗапрос Запрос Подготовка Обработка Отправка Алерт
  • 6.
    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); }
  • 7.
    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); });
  • 8.
  • 9.
    9 Асинхронность •  Производительность –  Кодвыше •  Интерфейс пользователя •  Проблемы –  Много лишнего шума –  Проблема синхронизации –  Куча вложенных колбэков: Pyramid of Doom •  Несколько реализаций –  Event Loop
  • 10.
    10 Event Loop •  Основавсех событийных систем •  Использует очередь событий •  Ждет события •  Выполняет события из очереди –  События в очередь поступают во время выполнения событий –  События генерируют события •  Завершается когда очередь пуста
  • 11.
  • 12.
    12 Типичный пример –обертка над XMLHttpRequest Callback 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); }
  • 13.
    13 Callback •  Самый простойвариант –  Дешевая абстракция •  В него могут приходить ошибки и данные –  cтиль node.js –  callback(err, data)
  • 14.
    14 Общая схема Event: EventEmitter,PubSub function EventEmitter () { this.events = {}; } EventEmitter.prototype = { on: function (event, callback) {}, off: function (event, callback) {}, emit: function (event, data) {} }; http://nodejs.org/api/events.html
  • 15.
    15 Типичный пример –обертка над XMLHttpRequest Event 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; }
  • 16.
    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(){ });
  • 17.
    17 Event •  Абстракция болеевысокого уровня •  Ошибки отделены от данных –  Возможны логически разные типы данных •  Можно отписаться от события •  Можно подписаться несколько раз •  Можно передавать как аргумент
  • 18.
    18 Promise •  Это Обещанныеданные •  Имеет 3 состояния –  Не выполнен (выполняется) –  Выполнен (результат) –  Отклонен (ошибка) •  Меняет состояние только 1 раз –  В событиях состояние меняется сколько угодно раз •  Запоминает свое состояние –  В отличии от события в котором состояние – это поток http://wiki.commonjs.org/wiki/Promises
  • 19.
    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) {} };
  • 20.
    20 Типичный пример –обертка над XMLHttpRequest Promise 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; }
  • 21.
    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!’); }); });
  • 22.
    22 Promise •  Запоминает своесостояние •  Всегда возвращает один результат –  В отличие от события где данные – поток –  Не зависит от времени опроса •  Можно передавать как аргумент •  Можно выполнять операции –  then
  • 23.
    23 Deferred •  Это защищенныйPromise •  Разграничивает слушателя и Promise •  Слушатель не может вмешаться –  С чистыми промисами можно завершить промис на слушателе –  Меньше логических ошибок http://api.jquery.com/category/deferred-object/
  • 24.
    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) {} };
  • 25.
    25 Типичный пример –обертка над XMLHttpRequest Deferred 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(); }
  • 26.
    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!’); // Это сделать нельзя
  • 27.
  • 28.
    28 Streamline – попыткаизбавится от асинхронного шума Используют callback(err, data) Streamline var data = asyncXHR('GET', '/', null, _); asyncXHR('POST', '/', data, _); asyncXHR('POST', '/', data, _); alert('Done!'); https://github.com/Sage/streamlinejs
  • 29.
    29 Happy Debug! Streamline –результат генерации (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);
  • 30.
    30 Streamlinejs •  Генерация кода– результат ужасен! •  Шум из массы _ •  Его цель – выполнять асинхронный код последовательно
  • 31.
    31 Fibers – попыткаизбавится от асинхронного шума Используют callback(err, data) Fibers 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 https://github.com/0ctave/node-sync
  • 32.
    32 Fibers •  Особая версияNode.js –  Хак механизма yield() •  Похожи на треды –  Не могут прерываться где угодно процессором –  Меньше расходов на «безопасные зоны» •  Похожи на Event Loop –  yield() и ручное прерывание фибера –  Блокировка остальных фиберов –  Нет реального параллелизма (не занимают все ядра процессора) •  Параллельные запросы последовательно –  Необходимо использовать дополнительные функции
  • 33.
    33 Позволяет выполнять асинхронныйкод в синхронном стиле Работает с callback(err, data) Step 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!’); } ); https://github.com/creationix/step
  • 34.
    34 Работает с Promise Представляетинтерфейс для работы с промисами Q 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) ]); } https://github.com/kriskowal/q
  • 35.
    35 Асинхронность •  Событийный ввод-вывод • Работа с GUI •  Паттерны –  Callback –  EventEmitter –  Promise –  Deferred •  Хаки –  Streamline –  Fibers •  Библиотеки –  Step –  Q
  • 36.