КРиПИ - JavaScript Асинхронность, таймеры, работа с сервером

КРиПИ - JavaScript Асинхронность, таймеры, работа с сервером

B827d6cfdfbcfce33700b0e6cc03e344?s=128

Mikhail Davydov

November 07, 2012
Tweet

Transcript

  1. None
  2. Михаил Давыдов Разработчик JavaScript Асинхронность, работа с сервером

  3. 3 Задача •  Качаем 1 файл •  Обрабатываем •  После

    отправляем данные на 2 сервера •  Вызываем alert()
  4. 4 Псевдокод программы var file = getFile('/filename.jpg'); file = jpg2png(file);

    sendFile(file, 'http://server1.ru/'); sendFile(file, 'http://server2.ru/'); alert('tada!');
  5. 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. 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. 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. 8 4. Алерт var file = getFile('/filename.jpg'); file = jpg2png(file);

    sendFile(file, 'http://server1.ru/'); sendFile(file, 'http://server2.ru/'); alert('tada!'); Рисуем окно через системное API
  9. 9 Схема загрузки линейной программы время Блокировка Блокировка Блокировка Загрузка

    Отправка Отправка Подготовка Обработка Отправка Алерт
  10. 10 Большую часть времени эта программа ждет I/O

  11. 11 Стоимость операций I/O • L1-кэш 3 цикла • L2-кэш 14 циклов

    • RAM 250 циклов • Диск 41 000 000 циклов • Сеть 240 000 000
  12. 12 На помощь приходят: треды, потоки, форки…

  13. 13 дедлоки, мьютексы, проблемы с синхронизацией и параллельное программирование

  14. 14

  15. Событийная программа

  16. 16 Идея событийного программирования •  Любое действие – событие – 

    Начало программы –  Клик на кнопку –  Событие во времени –  Конец чтения файла… •  Программа не ждет I/O –  Загрузка процесса предельно близка к 100% •  Подписывается на события I/O •  Выполняет код, когда событие наступило
  17. 17 Сообщи мне когда придет файл, а пока я буду

    делать что-то полезное
  18. 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. 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. 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. 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. 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. 23 Схема загрузки событийной программы время Ожидание Ожидание Запрос Подготовка

    Обработка Отправка Алерт
  24. 24 Профит •  Блокировка → Ожидание запроса •  Программа не

    блокируется •  Отправляет файлы параллельно •  1 тред может обслуживать несколько соединений
  25. 25 Event Loop

  26. 26 Event Loop •  Один поток •  Использует системные команды

    –  *NIX: select, epoll, kqueue –  Win: GetMessage, PeekMessage •  Основа – список событий •  Подписываемся на событие •  Выполняем код, когда событие произошло •  Список событий пуст – конец
  27. 27 Кадр или Фрейм Event Loop === обработчик события

  28. 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. 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. 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. 31 А что если будет несколько одновременных запросов?!

  32. 32 Фрейм 0 выполняем код программы Запрос к серверу Список

    событий Старт программы + Сейчас выполняется
  33. 33 Фрейм N пришел Запрос 1 Запрос к серверу Запрос

    к серверу Список событий Сейчас выполняется Файл прочитан 1 +
  34. 34 Фрейм N+1 пришел Запрос 2 Запрос к серверу Запрос

    к серверу Список событий Сейчас выполняется Файл прочитан 1 Файл прочитан 2 +
  35. 35 Фрейм N+2 прочитался Файл 1 Запрос к серверу Список

    событий Сейчас выполняется Файл прочитан 1 Файл прочитан 2 + Файл отправлен 1 Файл отправлен 1 +
  36. 36 Фрейм N+3 еще Запрос 3 Запрос к серверу Список

    событий Сейчас выполняется Файл прочитан 2 Файл отправлен 1 Файл отправлен 1 Запрос к серверу Файл прочитан 3 +
  37. 37 Фрейм N+4 Файлы 1 отправили Запрос к серверу Список

    событий Сейчас выполняется Файл прочитан 2 Файл прочитан 3 Файл отправлен 1 Файл отправлен 1 Затем
  38. 38 Фрейм N+5 Файлы 2 прочитали Запрос к серверу Список

    событий Сейчас выполняется Файл прочитан 3 Файл прочитан 2 + Файл отправлен 2 Файл отправлен 2 +
  39. 39 Фрейм N+6 Файлы 3 прочитали Запрос к серверу Список

    событий Сейчас выполняется Файл прочитан 3 Файл отправлен 2 Файл отправлен 2 + Файл отправлен 3 Файл отправлен 3 +
  40. 40 Фрейм N+7 Файлы 3 отправили Запрос к серверу Список

    событий Сейчас выполняется Файл отправлен 3 Файл отправлен 3 Затем Файл отправлен 2 Файл отправлен 2
  41. 41 Фрейм N+8 Файлы 2 отправили Запрос к серверу Список

    событий Сейчас выполняется Файл отправлен 2 Файл отправлен 2 Затем
  42. 42 Фрейм N+100500 убираем обработчик Список событий Убрать событие Сейчас

    выполняется
  43. 43 Когда очередь пуста – программа завершается

  44. 44 Таймеры в JavaScript

  45. 45 Таймеры это не sleep() – это события во времени,

    они используют Event Loop
  46. 46 Таймер без повтора •  setTimeout(function, timeout): Number –  выполни

    эту функцию не раньше чем через это время –  таймаут - миллисекунды •  clearTimeout(timerId) –  предотврати выполнение этого таймера –  ид таймера – обычное число
  47. 47 setTimeout(function () { console.log(1); }, 1000); var timerId =

    setTimeout(function () { console.log(2); }, 1000); console.log(3); clearTimeout(timerId); // 3, 1 Пример setTimeout
  48. 48 Таймер c повтором •  setInterval(function, timeout): Number –  выполняй

    эту функцию через данный интервал –  интервал - миллисекунды •  clearInterval(timerId) –  предотврати выполнение этого интрвала –  ид интервала – обычное число
  49. 49 var times = 10; var intervalId = setInterval(function ()

    { console.log(new Date()); times--; if (!times) { clearInterval(intervalId); } }, 1000); Пример setInterval
  50. 50 Любой таймер будет вызван не раньше указанного времени

  51. 51 var time = new Date(); setTimeout(function () { console.log(new

    Date() - time); }, 1000); // Эта функция выполняется 1100 мсек thisFunctionTakes1100msec(); // 1102 Пример промаха таймера
  52. 52 JavaScript работает в одном потоке и не может прерывать

    обработчики
  53. 53 Что происходит Получить текущее время Подписаться на событие T+1000

    Тяжелая функция (1100 мс) Время T+1000 Выполнение функции таймера Запаздывание
  54. 54 Таймеры выполняются в том же потоке, что и программа

  55. 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. 56 Таймер может никогда не выполниться

  57. 57 var time = new Date(); setTimeout(function () { console.log(new

    Date() - time); }, 1000); while(true); Пример не достижимого таймера
  58. 58 Время I/O > Время вычислений Лучше Event Loop

  59. 59 Время I/O < Время вычислений Лучше Thread или Fork

  60. 60 Работа с сервером

  61. 61 Асинхронная работа с сервером

  62. 62 AJAX – Асинхронный JavaScript и XML

  63. 63 Много разных API и хаков •  XMLHttpRequest •  EventSource

    •  WebSockets •  JSONP
  64. 64 XMLHttpRequest aka XHR •  Предполагали использовать XML •  Победил

    JSON •  XML остался
  65. 65 Возможности XMLHttpRequest •  Неблокирующие запросы –  GET, POST, PUT,

    DELETE, … –  Можно отправлять и блокирующие •  Нельзя отправлять на другой сервер –  В версии 2 можно
  66. 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. 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. 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. 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. 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. 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. 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. 73 Используем asyncXHR('get', 'http://site.ru', null, function (err, data) { if

    (!err) { console.log(data); } }); Стало на много меньше кода
  74. 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. 75 Заключение •  Линейная программа –  треды –  форки – 

    потоки •  Событийная программа –  Любой I/O – событие •  Event Loop •  Таймеры •  Асинхронная работа с сервером –  AJAX –  XMLHttpRequest aka XHR
  76. 76 Михаил Давыдов Разработчик JavaScript azproduction@yandex-team.ru azproduction Спасибо