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
9. Где применяется "асинхронность"
• Производительность
• Интерфейс пользователя
• Проблемы
– Много лишнего шума
– Проблема синхронизации
– Куча вложенных колбэков: Pyramid of Doom
• Несколько реализаций
– Event Loop
9
11. Event Loop
• Основа всех событийных систем
• Использует очередь событий
• Ждет события
• Выполняет события из очереди
– События в очередь поступают во время выполнения событий
– События генерируют события
• Завершается когда очередь пуста
11
12. Event Loop
Когда придет запрос к серверу – запусти этот код
Список событий
var servers = [
'http://serv1.ru/',
'http://serv2.ru/'];
getFile('filename.jpg').then(function (file){
file = jpg2png(file);
sendTo(file, servers).then(function (){
alert('tada!');
});
});
12
Запрос к серверу
13. Event Loop
Пришел запрос к северу, выполняем обработчик
Когда файл прочитается – запусти этот код
Список событий
var servers = [
'http://serv1.ru/',
'http://serv2.ru/'];
getFile('filename.jpg').then(function (file){
file = jpg2png(file);
sendTo(file, servers).then(function (){
alert('tada!');
});
});
13
Файл прочитан
14. Event Loop
Файл прочитался, выполняем обработчик
Когда файлы отправятся – запусти этот код
Список событий
var servers = [
'http://serv1.ru/',
'http://serv2.ru/'];
getFile('filename.jpg').then(function (file){
file = jpg2png(file);
sendTo(file, servers).then(function (){
alert('tada!');
});
});
14
Файл отправлен
Файл отправлен
15. А что если будет несколько
одновременных запросов?!
15
16. Фрейм 0 выполняем код программы
Сейчас выполняется
Старт программы
16
Список событий
+
Запрос к серверу
17. Фрейм N пришел Запрос 1
Сейчас выполняется
Список событий
Запрос к серверу
Запрос к серверу
+
17
Файл прочитан 1
18. Фрейм N+1 пришел Запрос 2
Сейчас выполняется
Список событий
Запрос к серверу
Запрос к серверу
Файл прочитан 1
+
18
Файл прочитан 2
19. Фрейм N+2 прочитался Файл 1
Сейчас выполняется
Список событий
Файл прочитан 1
Запрос к серверу
Файл прочитан 2
+
+
19
Файл отправлен 1
Файл отправлен 1
20. Фрейм N+3 еще Запрос 3
Сейчас выполняется
Список событий
Запрос к серверу
Запрос к серверу
Файл прочитан 2
Файл отправлен 1
Файл отправлен 1
+
20
Файл прочитан 3
21. Фрейм N+4 Файлы 1 отправили
Сейчас выполняется
Файл отправлен 1
Затем
Файл отправлен 1
Список событий
Запрос к серверу
Файл прочитан 2
Файл прочитан 3
21
22. Фрейм N+5 Файлы 2 прочитали
Сейчас выполняется
Список событий
Файл прочитан 2
Запрос к серверу
Файл прочитан 3
+
+
22
Файл отправлен 2
Файл отправлен 2
23. Фрейм N+6 Файлы 3 прочитали
Сейчас выполняется
Список событий
Файл прочитан 3
Запрос к серверу
Файл отправлен 2
Файл отправлен 2
+
+
23
Файл отправлен 3
Файл отправлен 3
24. Фрейм N+7 Файлы 3 отправили
Сейчас выполняется
Файл отправлен 3
Затем
Файл отправлен 3
Список событий
Запрос к серверу
Файл отправлен 2
Файл отправлен 2
24
25. Фрейм N+8 Файлы 2 отправили
Сейчас выполняется
Файл отправлен 2
Затем
Файл отправлен 2
25
Список событий
Запрос к серверу
29. Таймеры это не sleep() –
это события во времени,
они используют Event Loop
29
30. Таймер без повтора
• setTimeout(function, timeout): Number
– выполни эту функцию не раньше чем через это время
– таймаут - миллисекунды
• clearTimeout(timerId)
– предотврати выполнение этого таймера
– ид таймера – обычное число
30
31. Таймер c повтором
• setInterval(function, timeout): Number
– выполняй эту функцию через данный интервал
– интервал - миллисекунды
• clearInterval(timerId)
– предотврати выполнение этого интрвала
– ид интервала – обычное число
31
33. Пример промаха таймера
var time = new Date();
setTimeout(function () {
console.log(new Date() - time);
}, 1000);
// Эта функция выполняется 1100 мсек
thisFunctionTakes1100msec();
// 1102
33
35. Что происходит
Время
Получить текущее время
Подписаться на событие T+1000
Тяжелая функция (1100 мс)
T+1000
Запаздывание
Выполнение функции таймера
35
37. Еще один пример промаха таймера
var time = new Date();
setTimeout(function () {
console.log(new Date() - time);
}, 1000);
setTimeout(function () {
// Эта функция выполняется 1100 мсек
thisFunctionTakes1100msec();
}, 10);
thisFunctionTakes1100msec();
// 2212 = 1100 + 10 + 1100 + x
37
41. 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);
}
41
42. Callback
• Самый простой вариант
– Дешевая абстракция
• В него могут приходить ошибки и данные
– cтиль node.js
– callback(err, data)
42
43. Event: EventEmitter, PubSub
Общая схема
function EventEmitter () {
this.events = {};
}
EventEmitter.prototype = {
on: function (event, callback) {},
off: function (event, callback) {},
emit: function (event, data) {}
};
43
http://nodejs.org/api/events.html
44. 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;
}
44
45. 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(){ });
45
46. Event
• Абстракция более высокого уровня
• Ошибки отделены от данных
– Возможны логически разные типы данных
• Можно отписаться от события
• Можно подписаться несколько раз
• Можно передавать как аргумент
46
47. Promise
• Это Обещанные данные
• Имеет 3 состояния
– Не выполнен (выполняется)
– Выполнен (результат)
– Отклонен (ошибка)
• Меняет состояние только 1 раз
– В событиях состояние меняется сколько угодно раз
• Запоминает свое состояние
– В отличии от события в котором состояние – это поток
47
http://wiki.commonjs.org/wiki/Promises
48. 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) {}
};
48
49. 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;
}
49
50. 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)
];
return when(promises);
})
.then(function (data) {
alert('Done!');
})
50
51. Promise
• Запоминает свое состояние
• Всегда возвращает один результат
– В отличие от события где данные – поток
– Не зависит от времени опроса
• Можно передавать как аргумент
• Можно выполнять операции
– then
51
52. Deferred
• Это защищенный Promise
• Разграничивает слушателя и Promise
• Слушатель не может вмешаться
– С чистыми промисами можно завершить промис на слушателе
– Меньше логических ошибок
52
http://api.jquery.com/category/deferred-object/
53. Deferred
Общая схема
function Deferred () {
this._promise = {
then: function (fulfilled, rejected, progressed) {}
};
}
Deferred.prototype = {
promise: function (error) {
return this._promise;
},
reject: function (error) {},
resolve: function (data) {}
};
53
54. 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();
}
54
55. 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!’); // Это сделать нельзя
55
57. Streamline
Streamline – попытка избавится от асинхронного шума
Используют callback(err, data)
var data = asyncXHR('GET', '/', null, _);
asyncXHR('POST', '/', data, _);
asyncXHR('POST', '/', data, _);
alert('Done!');
57
https://github.com/Sage/streamlinejs
58. 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);
58
59. Streamlinejs
• Генерация кода – результат ужасен!
• Шум из массы _
• Его цель – выполнять асинхронный код
последовательно
59
60. 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!');
}
);
60
https://github.com/creationix/step
61. 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)
]);
}
61
https://github.com/kriskowal/q