Михаил Давыдов Разработчик JavaScript
Асинхронность, работа с сервером
3
Задача
• Качаем 1 файл • Обрабатываем • После отправляем данные на 2 сервера • Вызываем alert()
4
Псевдокод программы
var file = getFile('/filename.jpg'); file = jpg2png(file); sendFile(file, 'http://server1.ru/'); sendFile(file, 'http://server2.ru/'); alert('tada!');
5
1. Подготовка
var file = getFile('/filename.jpg'); file = jpg2png(file); sendFile(file, 'http://server1.ru/'); sendFile(file, 'http://server2.ru/'); alert('tada!');
Старт TCP/IP сессии Отправка HTTP запроса Получение данных …
6
2. Обработка
var file = getFile('/filename.jpg'); file = jpg2png(file); sendFile(file, 'http://server1.ru/'); sendFile(file, 'http://server2.ru/'); alert('tada!');
Переделываем JPG в PNG
7
3. Отправка
var file = getFile('/filename.jpg'); file = jpg2png(file); sendFile(file, 'http://server1.ru/'); sendFile(file, 'http://server2.ru/'); alert('tada!');
Старт TCP/IP сессии Отправка HTTP запроса Получение данных …
8
4. Алерт
var file = getFile('/filename.jpg'); file = jpg2png(file); sendFile(file, 'http://server1.ru/'); sendFile(file, 'http://server2.ru/'); alert('tada!');
Рисуем окно через системное API
9
Схема загрузки линейной программы
время
Блокировка Блокировка Блокировка
Загрузка Отправка Отправка
Подготовка Обработка Отправка Алерт
10
Большую часть времени эта программа ждет I/O
11
Стоимость операций I/O
• L1-кэш 3 цикла • L2-кэш 14 циклов • RAM 250 циклов • Диск 41 000 000 циклов • Сеть 240 000 000
12
На помощь приходят: треды, потоки, форки…
13
дедлоки, мьютексы, проблемы с синхронизацией и параллельное программирование
14
Событийная программа
16
Идея событийного программирования
• Любое действие – событие – Начало программы – Клик на кнопку – Событие во времени – Конец чтения файла…
• Программа не ждет I/O – Загрузка процесса предельно близка к 100%
• Подписывается на события I/O • Выполняет код, когда событие наступило
17
Сообщи мне когда придет файл, а пока я буду делать что-то полезное
18
Псевдокод событийной программы
var servers = [ 'http://serv1.ru/', 'http://serv2.ru/']; getFile('filename.jpg').then(function (file){ file = jpg2png(file); sendTo(file, servers).then(function (){ alert('tada!'); }); });
19
1. Подготовка
var servers = [ 'http://serv1.ru/', 'http://serv2.ru/']; getFile('filename.jpg').then(function (file){ file = jpg2png(file); sendTo(file, servers).then(function (){ alert('tada!'); }); });
Когда файл скачается вызови эту функцию
20
2. Обработка
var servers = [ 'http://serv1.ru/', 'http://serv2.ru/']; getFile('filename.jpg').then(function (file){ file = jpg2png(file); sendTo(file, servers).then(function (){ alert('tada!'); }); });
Кодируем в PNG
21
3. Отправка
var servers = [ 'http://serv1.ru/', 'http://serv2.ru/']; getFile('filename.jpg').then(function (file){ file = jpg2png(file); sendTo(file, servers).then(function (){ alert('tada!'); }); });
Когда файлы отправятся вызови эту функцию
22
4. Алерт
var servers = [ 'http://serv1.ru/', 'http://serv2.ru/']; getFile('filename.jpg').then(function (file){ file = jpg2png(file); sendTo(file, servers).then(function (){ alert('tada!'); }); });
Рисуем системное окно
23
Схема загрузки событийной программы
время
Ожидание Ожидание
Запрос
Подготовка Обработка Отправка Алерт
24
Профит
• Блокировка → Ожидание запроса • Программа не блокируется • Отправляет файлы параллельно • 1 тред может обслуживать несколько соединений
25
Event Loop
26
Event Loop
• Один поток • Использует системные команды
– *NIX: select, epoll, kqueue – Win: GetMessage, PeekMessage
• Основа – список событий • Подписываемся на событие • Выполняем код, когда событие произошло • Список событий пуст – конец
27
Кадр или Фрейм Event Loop === обработчик события
28
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!'); }); });
Список событий
Когда придет запрос к серверу – запусти этот код
Запрос к серверу
29
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!'); }); });
Список событий
Пришел запрос к северу, выполняем обработчик Когда файл прочитается – запусти этот код
Файл прочитан
30
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!'); }); });
Список событий
Файл прочитался, выполняем обработчик Когда файлы отправятся – запусти этот код
Файл отправлен
Файл отправлен
31
А что если будет несколько одновременных запросов?!
32
Фрейм 0 выполняем код программы
Запрос к серверу
Список событий
Старт программы + Сейчас выполняется
33
Фрейм N пришел Запрос 1
Запрос к серверу Запрос к серверу
Список событий Сейчас выполняется
Файл прочитан 1 +
34
Фрейм N+1 пришел Запрос 2
Запрос к серверу Запрос к серверу
Список событий Сейчас выполняется
Файл прочитан 1
Файл прочитан 2 +
35
Фрейм N+2 прочитался Файл 1
Запрос к серверу
Список событий Сейчас выполняется
Файл прочитан 1
Файл прочитан 2
+ Файл отправлен 1
Файл отправлен 1 +
36
Фрейм N+3 еще Запрос 3
Запрос к серверу
Список событий Сейчас выполняется
Файл прочитан 2
Файл отправлен 1
Файл отправлен 1
Запрос к серверу
Файл прочитан 3 +
37
Фрейм N+4 Файлы 1 отправили
Запрос к серверу
Список событий Сейчас выполняется
Файл прочитан 2
Файл прочитан 3
Файл отправлен 1
Файл отправлен 1
Затем
38
Фрейм N+5 Файлы 2 прочитали
Запрос к серверу
Список событий Сейчас выполняется
Файл прочитан 3
Файл прочитан 2
+ Файл отправлен 2
Файл отправлен 2 +
39
Фрейм N+6 Файлы 3 прочитали
Запрос к серверу
Список событий Сейчас выполняется
Файл прочитан 3
Файл отправлен 2
Файл отправлен 2
+ Файл отправлен 3
Файл отправлен 3 +
40
Фрейм N+7 Файлы 3 отправили
Запрос к серверу
Список событий Сейчас выполняется
Файл отправлен 3
Файл отправлен 3
Затем Файл отправлен 2
Файл отправлен 2
41
Фрейм N+8 Файлы 2 отправили
Запрос к серверу
Список событий Сейчас выполняется
Файл отправлен 2
Файл отправлен 2
Затем
42
Фрейм N+100500 убираем обработчик Список событий
Убрать событие
Сейчас выполняется
43
Когда очередь пуста – программа завершается
44
Таймеры в JavaScript
45
Таймеры это не sleep() – это события во времени, они используют Event Loop
46
Таймер без повтора
• setTimeout(function, timeout): Number – выполни эту функцию не раньше чем через это время – таймаут - миллисекунды
• clearTimeout(timerId) – предотврати выполнение этого таймера – ид таймера – обычное число
47
setTimeout(function () { console.log(1); }, 1000); var timerId = setTimeout(function () { console.log(2); }, 1000); console.log(3); clearTimeout(timerId); // 3, 1
Пример setTimeout
48
Таймер c повтором
• setInterval(function, timeout): Number – выполняй эту функцию через данный интервал – интервал - миллисекунды
• clearInterval(timerId) – предотврати выполнение этого интрвала – ид интервала – обычное число
49
var times = 10; var intervalId = setInterval(function () { console.log(new Date()); times--; if (!times) { clearInterval(intervalId); } }, 1000);
Пример setInterval
50
Любой таймер будет вызван не раньше указанного времени
51
var time = new Date(); setTimeout(function () { console.log(new Date() - time); }, 1000); // Эта функция выполняется 1100 мсек thisFunctionTakes1100msec(); // 1102
Пример промаха таймера
52
JavaScript работает в одном потоке и не может прерывать обработчики
53
Что происходит
Получить текущее время
Подписаться на событие T+1000
Тяжелая функция (1100 мс)
Время
T+1000
Выполнение функции таймера
Запаздывание
54
Таймеры выполняются в том же потоке, что и программа
55
var time = new Date(); setTimeout(function () { console.log(new Date() - time); }, 1000); setTimeout(function () { // Эта функция выполняется 1100 мсек thisFunctionTakes1100msec(); }, 10); thisFunctionTakes1100msec(); // 2212 = 1100 + 10 + 1100 + x
Еще один пример промаха таймера
56
Таймер может никогда не выполниться
57
var time = new Date(); setTimeout(function () { console.log(new Date() - time); }, 1000); while(true);
Пример не достижимого таймера
58
Время I/O > Время вычислений
Лучше Event Loop
59
Время I/O < Время вычислений
Лучше Thread или Fork
60
Работа с сервером
61
Асинхронная работа с сервером
62
AJAX – Асинхронный JavaScript и XML
63
Много разных API и хаков
• XMLHttpRequest • EventSource • WebSockets • JSONP
64
XMLHttpRequest aka XHR
• Предполагали использовать XML • Победил JSON • XML остался
65
Возможности XMLHttpRequest
• Неблокирующие запросы – GET, POST, PUT, DELETE, … – Можно отправлять и блокирующие
• Нельзя отправлять на другой сервер – В версии 2 можно
66
// GET запрос var xhr = new XMLHttpRequest(); // Подготавливаем запрос xhr.open('GET', 'http://server.ru/file.jpg', true); // Подписываемся на событие "изменение статуса" xhr.addEventListener('readystatechange', function () { // Когда ответ пришел if (xhr.readyState === 4) { // Печатаем тело ответа console.log(xhr.responseText); } }, false); // Отправляем запрос xhr.send();
Работа с XHR
67
Статусы XMLHttpRequest
• UNSENT=0 – функция open() еще не вызвана
• OPENED=1 – функция send() еще не вызвана
• HEADERS_RECEIVED=2 – Пришли заголовки
• LOADING=3 – часть ответа пришла
• DONE=4 – запрос завершен
https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest
68
Методы и свойства XHR • open(method, url, isNotBlock)
– method – 'get', 'post', … – url – 'http://pewpew.com', '/file.jpg', 'file.jpg', '//site.ru:8080/'
• send(body) – body – post тело 'name=name&time=1345678&message=hello'
• readyState: Number • responseText: String • status: Number
– HTTP статус ответа – 200, 404, 500
• addEventListener(event, function) • ...
https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest
69
Сделаем обертку над XMLHttpRequest
Асинхронный XHR
function asyncXHR(method, url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.addEventListener('readystatechange', function () { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.responseText); } else { callback('error'); } } }); xhr.send(data); }
70
Когда статус изменится – вызови эту функцию
Асинхронный XHR
function asyncXHR(method, url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.addEventListener('readystatechange', function () { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.responseText); } else { callback('error'); } } }); xhr.send(data); }
71
Если статус = "Готово" – проверяем статус ответа
Асинхронный XHR
function asyncXHR(method, url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.addEventListener('readystatechange', function () { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.responseText); } else { callback('error'); } } }); xhr.send(data); }
72
Если статус ответа 200 (все хорошо) – вызываем функцию с данными
Асинхронный XHR
function asyncXHR(method, url, data, callback) { var xhr = new XMLHttpRequest(); xhr.open(method, url, true); xhr.addEventListener('readystatechange', function () { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.responseText); } else { callback('error'); } } }); xhr.send(data); }
73
Используем
asyncXHR('get', 'http://site.ru', null, function (err, data) { if (!err) { console.log(data); } });
Стало на много меньше кода
74
Перепишем наш абстрактный пример
asyncXHR('get', 'filename.jpg', null, processThenSendFile); function processThenSendFile(err, file) { file = jpg2png(file); asyncXHR('post', '//site.ru/', file, alertWhenDone); } function alertWhenDone(err, status) { alert('tada'); }
75
Заключение
• Линейная программа – треды – форки – потоки
• Событийная программа – Любой I/O – событие
• Event Loop • Таймеры • Асинхронная работа с сервером
– AJAX – XMLHttpRequest aka XHR