$30 off During Our Annual Pro Sale. View Details »

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

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

Mikhail Davydov

November 07, 2012
Tweet

More Decks by Mikhail Davydov

Other Decks in Education

Transcript

  1. View Slide

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

    View Slide

  3. 3
    Задача
    •  Качаем 1 файл
    •  Обрабатываем
    •  После отправляем данные на 2 сервера
    •  Вызываем alert()

    View Slide

  4. 4
    Псевдокод программы
    var file = getFile('/filename.jpg');
    file = jpg2png(file);
    sendFile(file, 'http://server1.ru/');
    sendFile(file, 'http://server2.ru/');
    alert('tada!');

    View Slide

  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 запроса
    Получение данных

    View Slide

  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

    View Slide

  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 запроса
    Получение данных

    View Slide

  8. 8
    4. Алерт
    var file = getFile('/filename.jpg');
    file = jpg2png(file);
    sendFile(file, 'http://server1.ru/');
    sendFile(file, 'http://server2.ru/');
    alert('tada!');
    Рисуем окно через системное API

    View Slide

  9. 9
    Схема загрузки линейной программы
    время
    Блокировка
    Блокировка Блокировка
    Загрузка Отправка Отправка
    Подготовка Обработка Отправка Алерт

    View Slide

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

    View Slide

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

    View Slide

  12. 12
    На помощь приходят: треды,
    потоки, форки…

    View Slide

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

    View Slide

  14. 14

    View Slide

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

    View Slide

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

    View Slide

  17. 17
    Сообщи мне когда придет
    файл, а пока я буду делать
    что-то полезное

    View Slide

  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!');
    });
    });

    View Slide

  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!');
    });
    });
    Когда файл скачается вызови эту функцию

    View Slide

  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

    View Slide

  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!');
    });
    });
    Когда файлы отправятся вызови эту функцию

    View Slide

  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!');
    });
    });
    Рисуем системное окно

    View Slide

  23. 23
    Схема загрузки событийной программы
    время
    Ожидание Ожидание
    Запрос
    Подготовка Обработка Отправка Алерт

    View Slide

  24. 24
    Профит
    •  Блокировка → Ожидание запроса
    •  Программа не блокируется
    •  Отправляет файлы параллельно
    •  1 тред может обслуживать несколько
    соединений

    View Slide

  25. 25
    Event Loop

    View Slide

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

    View Slide

  27. 27
    Кадр или Фрейм Event Loop
    === обработчик события

    View Slide

  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!');
    });
    });
    Список событий
    Когда придет запрос к серверу – запусти этот код
    Запрос к серверу

    View Slide

  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!');
    });
    });
    Список событий
    Пришел запрос к северу, выполняем обработчик
    Когда файл прочитается – запусти этот код
    Файл прочитан

    View Slide

  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!');
    });
    });
    Список событий
    Файл прочитался, выполняем обработчик
    Когда файлы отправятся – запусти этот код
    Файл отправлен
    Файл отправлен

    View Slide

  31. 31
    А что если будет несколько
    одновременных запросов?!

    View Slide

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

    Сейчас выполняется

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. 45
    Таймеры это не sleep() –
    это события во времени,
    они используют Event Loop

    View Slide

  46. 46
    Таймер без повтора
    •  setTimeout(function, timeout): Number
    –  выполни эту функцию не раньше чем через это время
    –  таймаут - миллисекунды
    •  clearTimeout(timerId)
    –  предотврати выполнение этого таймера
    –  ид таймера – обычное число

    View Slide

  47. 47
    setTimeout(function () {
    console.log(1);
    }, 1000);
    var timerId = setTimeout(function () {
    console.log(2);
    }, 1000);
    console.log(3);
    clearTimeout(timerId);
    // 3, 1
    Пример setTimeout

    View Slide

  48. 48
    Таймер c повтором
    •  setInterval(function, timeout): Number
    –  выполняй эту функцию через данный интервал
    –  интервал - миллисекунды
    •  clearInterval(timerId)
    –  предотврати выполнение этого интрвала
    –  ид интервала – обычное число

    View Slide

  49. 49
    var times = 10;
    var intervalId = setInterval(function () {
    console.log(new Date());
    times--;
    if (!times) {
    clearInterval(intervalId);
    }
    }, 1000);
    Пример setInterval

    View Slide

  50. 50
    Любой таймер будет вызван
    не раньше указанного
    времени

    View Slide

  51. 51
    var time = new Date();
    setTimeout(function () {
    console.log(new Date() - time);
    }, 1000);
    // Эта функция выполняется 1100 мсек
    thisFunctionTakes1100msec();
    // 1102
    Пример промаха таймера

    View Slide

  52. 52
    JavaScript работает
    в одном потоке и не может
    прерывать обработчики

    View Slide

  53. 53
    Что происходит
    Получить текущее время
    Подписаться на событие T+1000
    Тяжелая функция (1100 мс)
    Время
    T+1000
    Выполнение функции таймера
    Запаздывание

    View Slide

  54. 54
    Таймеры выполняются в
    том же потоке, что и
    программа

    View Slide

  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
    Еще один пример промаха таймера

    View Slide

  56. 56
    Таймер может никогда не
    выполниться

    View Slide

  57. 57
    var time = new Date();
    setTimeout(function () {
    console.log(new Date() - time);
    }, 1000);
    while(true);
    Пример не достижимого таймера

    View Slide

  58. 58
    Время I/O > Время вычислений
    Лучше Event Loop

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  65. 65
    Возможности XMLHttpRequest
    •  Неблокирующие запросы
    –  GET, POST, PUT, DELETE, …
    –  Можно отправлять и блокирующие
    •  Нельзя отправлять на другой сервер
    –  В версии 2 можно

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  73. 73
    Используем
    asyncXHR('get', 'http://site.ru', null,
    function (err, data) {
    if (!err) {
    console.log(data);
    }
    });
    Стало на много меньше кода

    View Slide

  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');
    }

    View Slide

  75. 75
    Заключение
    •  Линейная программа
    –  треды
    –  форки
    –  потоки
    •  Событийная программа
    –  Любой I/O – событие
    •  Event Loop
    •  Таймеры
    •  Асинхронная работа с сервером
    –  AJAX
    –  XMLHttpRequest aka XHR

    View Slide

  76. 76
    Михаил Давыдов
    Разработчик JavaScript
    [email protected]
    azproduction
    Спасибо

    View Slide