JavaScript: методы асинхронного программирования. Синхронный и асинхронный код

Коды (асинхронный и синхронный): формирование и установка

Достаточно часто у наших клиентов возникают вопросы относительно того, как будет влиять наличие встроенного счетчика на работу сайта. Скрипты могут увеличивать время загрузки страницы.

Ранее в Google Analytics предлагался стандартный код, который влиял на скорость загрузки страницы и на сбор данных.

На текущий момент Google Analytics предлагает асинхронную версию кода, которая обладает рядом преимуществ относительно стандартного кода.

Асинхронный код – это улучшенный фрагмент кода JavaScript, который загружает код отслеживания ga.js в фоновом режиме, без приостановки загрузки других скриптов и контента на страницах вашего веб-сайта.

Преимущества использования асинхронного кода:

  • Более точный сбор данных о коротких посещениях
  • Если клики произошли до загрузки кода, данные будут сохранены
  • Если у вас установлена стандартная версия кода, рекомендуется обновить код на асинхронный. Для этого необходимо удалить старый фрагмент кода, и на его место встроить асинхронный код. После установки асинхронного кода данные начнут появляться в аккаунте в течение 24 часов.

    • не использовать 2 кода одновременно, это может привести к неточности данных;
    • асинхронный код располагается внизу раздела , в отличие от стандартного, который располагался непосредственно перед закрывающим тегом .

    Формирование и установка кода

    1. Код создается на уровне профиля. Необходимо перейти на вкладку «Администратор».

    3. Полученный код необходимо скопировать и встроить на страницу перед закрывающим тегом .

    *Одним из главных преимуществ асинхронного кода является то, что его можно добавить в верхней части HTML-кода. В результате повышается вероятность передачи информации о посещении до того, как пользователь покинет страницу.

    В данной статье мы не рассматривали модификации кода, для этого будет написан отдельный пост.

    Асинхронное программирование сегодня достаточно востребовано. Особенно в веб-разработке, где скорость реагирования приложения играет огромную роль. Никто не хочет тратить свое время и ожидать подвисшее приложение, пока вы выполняете несколько запросов к базе, отправляете электронное письмо или запускаете другие потенциально длительные задачи. Пользователи хотят получать ответы на свои действия, и они хотят, чтобы эти ответы происходили мгновенно. Когда ваше приложение становится медленным, вы начнете терять своих клиентов. После того как пользователь сталкивается с подвисанием приложения, в большинстве случаев он или она просто закрывает его и никогда не возвращается. Когда интерфейс зависает, с точки зрения пользователя не ясно — то ли ваше приложение поломано, то ли оно выполняет какие-то длительные задачи и на них требуется некоторое время.

    Отзывчивость

    Современные приложения, как правило, отзывчивы , но некоторые потенциально долгие задачи или блокирующие операции, такие как сетевые и файловые системы ввода/вывода или запросы к базе могут существенно замедлить работу приложения. Чтобы предотвратить приложения от блокировки с помощью этих операций мы можем запускать их в фоновом режиме, таким образом, скрывая задержки, которые они создают. Итак, приложение остается отзывчивым , т. к. оно может продолжить работу, например, оно может вернуть управление пользовательскому интерфейсу или ответить на другие события.

    Параллельность vs асинхронность

    Большинство людей, когда они видят асинхронный код, сразу думают “О, это круто! Я могу запустить мои задачи параллельно!” . Я, может вас разочарую, но на самом деле, это неправда, конкурентность (асинхронность) и параллельность — это не то же самое. Это часто встречающаяся ошибка, поэтому давайте попробуем разобраться почему.

    Когда что-то выполняется асинхронно, это означает не блокирующее выполнение без ожидания завершения. В то время как параллельность означает выполнение нескольких отдельных задач в одно и то же время, как независимые части.

    Асинхронность:

    Сделайте задачу сами в какое-либо свободное время и дайте мне знать, когда закончите, и принесите мне результаты. В это время я могу продолжить выполнение своей задачи.

    Асинхронный код требует обработки зависимостей между последовательностью выполнения и делается это с помощью обратных вызовов. Когда какое-то задание выполнилось, оно уведомляет другое задание о том, что оно завершилось. Асинхронный код в основном имеет дело со временем (последовательностью событий).

    Параллельность:

    Наймите столько людей, сколько вы хотите и разделите задачу между ними, чтобы выполнить её быстрее и сообщите мне, когда закончите. Я могу продолжить заниматься своими задачами или, если задача срочная, я останусь здесь и подожду, пока вы вернетесь с результатами. После я могу объединить результаты этих ребят. Параллельное выполнение зачастую требует больше ресурсов, т. е. оно в основном зависит от железа.

    Чтобы проиллюстрировать разницу между асинхронным и параллельным выполнением на реальных примерах, мы можем сравнить два популярных веб-сервера: Apache и Nginx. Они прекрасно иллюстрируют эту разницу: Nginx является асинхронным и основан на событиях, в то время как Apache использует параллельные потоки. Apache создает новые потоки для каждого дополнительного подключения, поэтому существует максимально допустимое количество соединений в зависимости от доступной памяти в системе. При достижении этого лимита подключений, Apache отказывается от дополнительных соединений. Ограничивающим фактором в настройке Apache является память (помните, что параллельное выполнение часто зависит от аппаратного обеспечения). Если поток останавливается, клиент ждет ответа, пока поток освободится и вернёт ответ.

    Nginx работает иначе, чем Apache и не создаёт новые потоки для каждого входящего запроса. У него есть основной процесс-воркер (или несколько процессов, зачастую на практике рекомендуется иметь по одному воркеру на каждый процессор(CPU)), который является однопоточным. Этот воркер может обрабатывать тысячи одновременных подключений. Он делает это асинхронно с одним потоком, а не с помощью многопоточного параллельного выполнения.

    Итак, асинхронность (или конкурентность) — это способ выполнения задач. Она представляет собой композицию из независимо выполняющихся задач. Параллельность — это одновременное выполнение нескольких задач (они могут быть связаны, а могут и нет). В асинхронности, мы имеем дело со многими разными задачами одновременно. Параллельность делает много задач сразу. Звучит одинаково, но в основе разные идеи. Конкурентность — это о структуре, в то время как параллельность — об исполнении.

    От переводчика:

    Асинхронность : у вас есть бекенд задача, которую вы делаете в данный момент. Тут к вам обратился фронтенд разработчик с просьбой немого поправить API. Вы сохранились у себя в ветке, переключились на другую (в данный момент вы заняты задачей с API), поправили и вернулись к своей задаче. После вас позвали на совещание по какой-то тематике, вы прервались, сходили (в данный момент вы выполняете третью задачу, которая будет продолжена на следующем совещании) и вернулись опять к своей первой задаче. Эти три задачи вы выполняли асинхронно — другими словами, прерываясь и переключаясь между задачами, выделяя на них понемногу времени.

    Параллельность : если говорить о параллельности в лице одного человека, то в голову приходят пара очевидных примеров. Например, играть на гитаре и петь. В этот момент одна ваша рука ставит аккорды, другая выполняет перебор (или бой) и плюс вы ещё поёте. В этот момент вы параллельно выполняете три задачи. Или другой пример: вы за обедом слушаете Моцарта. Тут вы параллельно выполняете две задачи — едите и слушаете. Но если вернуться к задачам разработки, то получится более наглядный пример. Представьте, что вы работаете в команде из 4 разработчиков. Ваша команда — это эдакая единая машина с четырехъядерным процессором. Каждый разработчик — одно ядро. Когда начинается спринт, каждый из 4 разработчиков выполняет свои задачи параллельно с остальными тремя разработчиками, а в конце спринта вы собираете это воедино.

    Зачем на бекенде?

    Теперь вы можете возмутиться на то, что на беке вас не особо волнует отзывчивость. У вас на фронте есть все эти гадкие вещи типа асинхронного JavaScript-а, а всё, что должен делать ваш сервер, — это просто отвечать на запросы. Так что обеспечить отзывчивость пользователю — это задача фронта, а не ваша. Да, это правда, но бекенд не ограничивается только API-ответами. Иногда вам нужно управлять какими-то сложными задачами, например, сервер для загрузки видео. В данном случае, возможно, отклик не является ключевым фактором, но мы упираемся в нехватку ресурсов , потому что приложение должно ждать. Он может ждать операций файловой системы, сетевого подключения, запросов к базе и так далее. Часто эти операции ввода/вывода осуществляются крайне медленно по сравнению с расчетами на CPU, например, когда мы конвертируем видеофайлы. А пока мы медленно сохраняем или читаем файл, наш процессор должен ждать и ничего не делать, вместо того, чтобы делать какую-то полезную работу. Как мы уже говорили, вместо ожидания, мы можем выполнять эти задачи в фоновом режиме. Как? Читайте ниже.

    Асинхронный РНР

    JavaScript мир уже имеет встроенную поддержку и инструменты для написания асинхронного кода. И также есть NodeJs, который позволяет писать асинхронные приложения. В JavaScript, мы можем использовать функцию setTimeout() , чтобы продемонстрировать асинхронный код:

    SetTimeout(function() { console.log("After timeout"); }, 1); console.log("Before timeout");

    При выполнении этого кода мы видим следующее:

    Before timeout After timeout

    Функция setTimeout() ставит в очередь код, который будет выполняться после завершения текущего стека вызовов. Это означает, что мы нарушаем синхронный поток кода и откладываем выполнение некоторой части кода. Второй вызов console.log() будет выполнен до первого (внутри функции settimeout()), который был поставлен в очередь.

    Но как на счёт PHP? Ну, в PHP из коробки у нас нет хороших и удобных инструментов для написания действительно асинхронного кода. Нет функции эквивалентной settimeout() и мы просто не можем отложить или поставить в очередь какой-то код. Вот почему такие фреймворки и библиотеки, как Amp и ReactPHP , начали появляться. Их основная идея в том, чтобы скрыть от нас низкоуровневые тонкости языка и предоставить инструменты и абстракции высокого уровня, которые могут быть использованы для написания асинхронного кода и управления конкурентностью, как мы могли бы сделать это в JavaScript и NodeJS.

    Почему я должен использовать PHP, если у нас есть NodeJs и Go?

    Подобный вопрос возникает чаще всего, когда речь идет об асинхронном РНР. Почему-то сообщество часто против использования PHP в качестве инструмента для написания асинхронного кода. Всегда кто-то предлагает просто использовать Go и NodeJs .

    Мы выпустили новую книгу «Контент-маркетинг в социальных сетях: Как засесть в голову подписчиков и влюбить их в свой бренд».

    Подписаться

    Что такое асинхронный код счетчика?

    Асинхронный код отслеживания (счетчика) - это код скрипта, вставляемого в тело сайта, разработанный для отслеживания статистики посещаемости. К ним относятся скрипты Google Analytics и Яндекс.Метрики.

    Асинхронным этот код называется из-за того, что выполняется параллельно всем остальным скриптам. Что это значит?

    По умолчанию, все JavaScript выполняются последовательно, и, если в первом коде есть ошибка или элемент, задерживающий загрузку остальных скриптов, то в отслеживание статистики посещений может закрасться ошибка. Асинхронный код выполняется параллельно остальным процессам и самым первым при загрузке страницы. Это обеспечивает его 100% срабатывание и точный сбор данных.

    Данные, отправляемые этим скриптом, формируют статистические объекты. К ним относятся:

    • просмотр страницы;
    • внешний переход;
    • пользователь.

    Эти данные нужны для того, чтобы сформировать общую картину о сайте: количество посетителей, их пол и возраст, географию и время, проведенное на сайте, страницы входа и выхода, и т. д.

    Где взять код Google Analytics и Яндекс.Метрики?

    Чтобы получить код Google Analytics вам необходимо:

    1. Зарегистрироваться или войти в сервисы Google.
    2. Зайти во вкладку «Администраторы», заполнить поля «Название аккаунта», «Название сайта», «URL сайта». Для каждого нового сайта рекомендуется создавать новый аккаунт.

    3. Принять условия использования.

    1. После этого вам станут доступны идентификаторы и код отслеживания Google Analytics.

    Чтобы получить ассинхронный код в Яндекс. Метрике:

    1. Зарегистрируйтесь или авторизируйтесь на сервисе.
    2. Нажмите кнопку «Добавить счетчик».

    1. Откроется окно нового счетчика. Заполните поля, поставьте галочку «Я принимаю условия».

    1. Вашему счетчику сайту будет присвоен номер. В разделе «Код счётчика» вы найдете код, который сможете вставить в сайт.

    Куда ставить код Google Analytics и Яндекс.Метрики?

    Новый код отслеживания ставится в самое начало страницы - в блок . В основном, это делается через редактор или FTP.

    Но, если вы, например, владелец сайта на WordPress, то для вас доступны специальные плагины с кодом «Google Analytics for WordPress» или Google Analyticator. Вы просто вставляете ваш Tracking ID в поле Analytics Profile, и система выполняет авторизацию самостоятельно.

    Владельцы CMS OpenCart могут вставить код Google Analytics, выбрав вкладки Extensions -> Analytics и вставив код счётчика в соответствующее поле Google Analytics.

    Также владельцы динамических.php-сайтов могут самостоятельно вставить код в файл header. php, но это грозит тем, что код пропадет при обновлении системы, и его придётся размещать заново. Либо создать файл analyticstracking. php и добавить его во все шаблоны страниц PHP со строкой кода сразу после открывающего тега .

    Сегодня каждый день появляются новые языки программирования - Go, Rust, CoffeeScript - все, что угодно. Я решил, что я тоже горазд придумать свой язык программирования, что миру не хватает какого-то нового языка…

    Дамы и господа, я представляю вам сегодня Schlecht!Script - чумовой язык программирования. Мы все должны начать им пользоваться прямо сейчас. В нем есть все то, к чему мы привыкли - в нем есть условные операторы, есть циклы, есть функции и функции высших порядков. В общем, в нем есть все, что нужно нормальному языку программирования.

    Что в нем не очень обычно, что может даже оттолкнуть, на первый взгляд, - это то, что в Schlecht!Script функции имеют цвет.


    Т.е., когда вы объявляете функцию, когда вы ее вызываете, вы явным образом указываете ее цвет.

    Функции бывают красные и синие - двух цветов.

    Важный момент: внутри синих функций вы можете вызывать только другие синие функции. Вы не можете вызывать красные функции внутри синих функций.


    Внутри красных функций вы можете вызывать и красные, и синие функции.


    Я решил, что должно быть так. В каждом языке должно быть так.

    Тонкий момент: красные функции писать и вызывать больно! Что я имею в виду, когда говорю «больно»? Дело в том, что сейчас я изучаю немецкий язык, и я решил, что красные функции мы должны все называть только на немецком языке, иначе интерпретатор просто не поймет, что вы ему пытаетесь впихнуть, и он просто не будет это выполнять.


    Вот так вы должны писать функции на немецком языке:


    «!» обязателен - мы же на немецком пишем, в конце концов.

    Как писать на таком языке? У нас есть два способа. Мы можем использовать только синие функции, в которые писать не больно, но внутри мы не можем пользоваться красными функциями. Этот подход не будет работать, потому что в порыве вдохновения я написал половину стандартной библиотеки на красных функциях, так что, простите…

    Вопрос к вам - стали бы вы использовать такой язык? Продал ли я вам Schlecht!Script?

    Ну, у вас, как бы, нет выбора. Простите…

    JavaScript - отличный язык, мы все его любим, мы все здесь собрались, потому что мы любим JavaScript. Но проблема в том, что JavaScript наследует некоторые черты Schlecht!Script, и я, конечно, не хочу хвастаться, но, по-моему, они украли пару моих идей.

    Что именно они наследуют? в JavaScript есть красные и синие функции. Красные функции в JavaScript - это асинхронные функции, синие - синхронные функции. И все прослеживается, все та же цепочка… Красные функции вызывать больно в Schlecht!Script, а асинхронные функции вызывать больно в JavaScript.

    И внутри синих функций мы не можем писать красные функции. Я еще скажу об этом позже.


    Почему это больно? Откуда боль при вызове и при написании асинхронных функций?

    У нас по-другому работают условные операторы, циклы, return. У нас не работает try/catch, и асинхронные функции ломают абстракцию.

    О каждом пункте немного подробнее.


    Вот так выглядит синхронный код, где shouldProcess и process - это функции синхронные, и работают условные операторы, работает for, в общем, все хорошо.

    То же самое, но асинхронное, будет выглядеть вот так вот:

    Там появилась рекурсия, мы передаем состояние в параметры, в функцию. В общем, смотреть прямо-таки неприятно. У нас не работает try/catch и, я думаю, мы все знаем, что если мы обернем синхронный блок кода в try/catch, исключения мы не поймаем. Нам нужно будет передать callback, перенавесить обработчик событий, в общем, у нас нет try/catch…

    И асинхронные функции ломают абстракцию. Что я имею в виду? Представьте, что вы написали кэш. Вы сделали кэш пользователей в памяти. И у вас есть функция, которая читает из этого кэша, которая, естественно, синхронная, потому что все в памяти. Завтра к вам приходят тысячи, миллионы, миллиарды пользователей, и вам нужно положить этот кэш в Redis. Вы кладете кэш в Redis, функция становится асинхронной, потому что из-за Redis"а мы можем читать только асинхронно. И, соответственно, весь стек, который вызывал вашу синхронную функцию, придется переписать, потому что теперь весь стек становится асинхронным. Если какая-то функция зависела от функции чтения из кэша, она так же будет теперь асинхронной.

    В общем и целом, говоря об асинхронности в JavaScript, можно сказать, что все там грустно.

    Но что мы все об асинхронности? Давайте немного обо мне поговорим, наконец.


    Я пришел, чтобы вас всех спасти. Ну, я попробую это сделать.

    Меня зовут Андрей, я работаю в стартапе «Productive Mobile» в Берлине. Я помогаю с организацией MoscowJS и я являюсь соведущим RadioJS. Мне очень интересна тема асинхронности и не только в JavaScript, я считаю, что, в принципе, это определяющий момент языка. То, как язык работает с асинхронностью, определяет его успех и то, насколько людям приятно и удобно с ним работать.

    Говоря об асинхронности конкретно в JavaScript, мне кажется, у нас есть два сценария, с которыми мы постоянно взаимодействуем. Это обработка множества событий и обработка единичных асинхронных операций.

    Множество событий - это, например, событие DOM или подключение к серверу - что-то, что излучает множество нескольких типов событий.

    Единичная операция - это, к примеру, чтение из базы. Единичная асинхронная операция возвращает нам либо один результат, либо возвращает ошибку. Больше никаких вариантов нет.

    И, говоря об этих двух сценариях, интересно порассуждать: вот, типа, асинхронность - плохо, в общем, все грустно… А что мы на самом деле хотим? Как бы выглядел идеальный асинхронный код?


    А хотим мы, мне кажется, контроля потока управления. Мы хотим, чтобы наши условные операторы, циклы работали в синхронном коде так же, как в асинхронном.

    Мы хотим обработки исключений. Зачем нам try/catch, если мы не можем его использовать в асинхронных операциях? Это просто странно.

    И желательно, конечно, иметь единый интерфейс. Почему асинхронная функция должна писаться и вызываться по-другому, по сравнению с синхронной? Этого не должно быть.

    Вот, чего мы хотим.

    А что у нас есть сегодня, и какие инструменты у нас появятся в будущем?


    Если мы говорим об ECMAScript 6 (это, в принципе, то, о чем я буду говорить сегодня), для работы с множеством событий у нас есть EventEmitter и Stream, а для работы с единичными асинхронными операциями - Continuation Passing Style (они же callback"и), Promises и Coroutines.


    В ECMAScript 7 у нас появятся Async Generators для работы с множеством событий и Async/Await - для работы с единичными асинхронными операциями.

    Об этом и поговорим.

    Начнем с того, что у нас есть в ECMAScript 6 для работы с множеством асинхронных событий. Напомню, например, это обработка событий мыши или нажатий на клавиши. У нас есть паттерн EventEmitter, который реализован в браузере в Node.js. Он встречается практически в любой API, где мы работаем с множеством событий. EventEmitter говорит нам, что мы можем создать объект, который излучает события, и навешивать обработчики на каждый тип события.


    Интерфейс очень простой. Мы можем добавлять EventListener, убирать EventListener по названию event"а, передавая туда сallback.


    К примеру, в XMLHttpRequest, когда я говорю о множестве событий, я имею в виду, что у нас может быть множество событий progress. Т.е. по мере того, как мы загружаем какие-то данные с помощью AJAX-запроса, нам выстреливают события progress, и по одному разу выстреливают события load, abort и error:


    Error - это особенное событие, универсальное событие в EventEmitter"ах и в Stream"ах для того чтобы уведомить пользователя об ошибке.

    Есть множество реализаций:


    Здесь перечислены только несколько, и в конце доклада будет ссылка, где все эти реализации есть.

    Важно сказать, что в Node.js EventEmitter встроен по умолчанию.

    Итак, это то, что у нас есть практически по стандарту в API и браузерах в Node.js.

    Что у нас еще есть для работы с множеством событий? Stream.

    Stream - это поток данных. Что такое данные? Это могут быть бинарные данные, например, данные из файла, текстовые данные, либо объекты или события. Самые популярные примеры:


    Есть несколько типов потоков:


    Здесь мы рассматриваем цепочку преобразований из Stylus файлов в css файлы, добавляя автопрефиксер, потому что все мы любим Андрея Ситника и его автопрефиксер.

    Вы видите, что у нас есть несколько типов потоков - поток-источник это gulp.src, который читает файлы и излучает объекты файлы, которые потом идут в потоки преобразования. Первый поток преобразования делает из stylus файла css, второй поток преобразования добавляет префиксы. И последний тип потоков - это поток-потребитель, который принимает эти объекты, пишет что-то куда-то на диск, и ничего не излучает.


    Т.е. у нас есть 3 типа потоков - источник данных, преобразование и потребитель. И эти паттерны прослеживаются везде, не только в gulp, но и при наблюдении за DOM событиями. У нас есть потоки, которые излучают DOM-события, которые их преобразуют и что-то, что потребляя эти DOM-события, возвращает конкретный результат.

    Это то, что можно назвать конвейером. С помощью потоков мы можем выстраивать такие цепочки, когда объект кладется куда-то в начало конвейера, проходит цепочку преобразований и, когда к нему подходят люди, что-то там меняют, добавляют, удаляют, и в конечном итоге у нас выходит какой-нибудь автомобиль.

    Есть несколько реализаций потоков, или они же Observables:


    В Node.js потоки встроенные - это Node Streams.

    Итак, у нас есть EventEmitter и Stream. EventEmitter по умолчанию есть во всех API, а. Stream - это надстройка, которую мы можем использовать для того, чтобы унифицировать интерфейс обработки множества событий.


    Когда мы говорим о тех критериях, по которым мы сравниваем асинхронные API, у нас не работают, по большому счету, операторы return, операторы-циклы, у нас не работают try/catch, естественно, и до единого интерфейса с синхронными операциями нам еще далеко.

    В общем, для работы с множеством событий в ECMAScript 6 все не очень хорошо.

    Когда мы говорим об единичных асинхронных операциях, у нас есть 3 подхода в ECMAScript 6:


    Continuation Passing Style, они же - callback"и.


    Я думаю, вы все к этому уже привыкли. Это когда мы делаем асинхронный запрос, передаем туда callback, и callback будет вызван либо с ошибкой, либо с результатом. Это распространенный подход, он есть и в браузере в Node.


    Проблемы с этим подходом, я думаю, вы тоже все понимаете.


    Вот так бы мы получали ленту твитов пользователей асинхронно, если бы все функции были синхронными.


    Тот же самый код, но синхронно, выглядит следующим образом:


    Можно увеличить шрифт, чтобы было виднее. И еще немного увеличить… И мы прекрасно понимаем, что это Schlecht!Script.


    Я говорил, что они украли мою идею.

    Continuation Passing Style - это стандартный API в браузере в Node.js, мы работаем с ними постоянно, но это неудобно. Поэтому у нас есть Promises. Это объект, который представляет собой асинхронную операцию.


    Promise - это как бы обещание что-то сделать в будущем, асинхронно.


    Мы можем навешивать callback"и на асинхронную операцию с помощью метода then, и это, в принципе, основной метод в API. И мы можем, очень важно, чейнить Promises, мы можем вызывать then последовательно, и каждая функция, которая передается в then, также может возвращать Promises. Вот так выглядит запрос ленты пользователя из твиттера на Promises.

    Если мы сравниваем этот подход с Continuation Passing Style, Promises, естественно, удобнее пользоваться - они дают нам возможность писать гораздо меньше бойлерплейта.

    Continuation Passing Style, по-прежнему, используется во всех API по умолчанию, в Node.js, в io.js., и они даже не планируют переходы на Promises по нескольким причинам. Сначала многие говорили, что причины - это производительность. И это действительно так, исследования 2013 года показывают, что Promises сильно позади callback"ов. Но с появлением таких библиотек как bluebird, мы уже можем уверенно сказать, что это не так, потому что Promises в bluebird приближаются в производительности к callback"ам. Важный момент: почему Promises не рекомендуют использовать в API до сих пор? Потому что, когда вы выдаете Promises из вашей API, вы навязываете имплементацию.

    Все Promises библиотеки должны подчиняться стандарту, но выдавая Promises, вы так же выдаете и имплементацию, т.е. если вы написали свой код, используя медленные Promises, и выдаете медленный Promises из API, это будет не очень приятно пользователям. Поэтому для внешних API, конечно же, по-прежнему рекомендуют использовать callback"и.


    Реализаций Promises - масса, и если вы не написали свою реализацию, вы ненастоящий JavaScript-программист. Я не написал свою реализацию Promises, поэтому мне пришлось придумать свой язык.

    Итак, Promises, в общем, чуть меньше бойлерплейта, но, тем не менее, все еще не так хорошо.

    Что по поводу Coroutines? Здесь уже начинаются интересные штуки. Представьте…

    Это интересно. Мы были на JSConf в Будапеште, и там был сумасшедший человек, который на JavaScript программировал квадракоптер и что-то еще, и половину из того, что он пытался нам показать, у него не получалось. Поэтому он постоянно говорил: «OK, теперь представьте… Этот квадракоптер взлетел и все получилось...».

    Представьте, что мы можем поставить функцию на паузу в какой-то момент ее выполнения.


    Здесь функция получает имя пользователя, она лезет в базу, получает объект пользователя, возвращает его имя. Естественно, «залезть в базу» - функция getUser асинхронная. Что, если мы могли бы поставить функцию getUserName на паузу в момент вызова getUser? Вот, мы выполняем нашу getUserName функцию, дошли до getUser и остановились. getUser сходил в базу, получил объект, вернул его в функцию, мы продолжаем выполнение. Насколько круто это было бы.

    Дело в том, что Coroutines дают нам эту возможность. Coroutines - это функция, которую мы можем приостанавливать и возобновлять в любой момент времени. Важный момент: мы не останавливаем всю программу.


    Это не блокирующая операция. Мы останавливаем выполнение конкретной функции в конкретном месте.


    Как getUserName выглядит с помощью генераторов на JavaScript? Нам нужно добавить «*» в объявление функции, чтобы сказать о том, что функция возвращает генератор. Мы можем использовать ключевое слово «yield» в том месте, где мы хотим поставить функцию на паузу. И важно помнить, что getUser здесь возвращает Promises.

    Т.к. генераторы изначально придумали для того чтобы делать ленивые последовательности в JavaScript, по большому счету, использовать их для синхронного кода - это хак. Поэтому нам нужны библиотеки, чтобы как-то это компенсировать.


    Здесь мы используем «co» для того, чтобы обернуть генератор и вернуть нам асинхронную функцию.

    Итого, вот, что у нас получается:


    У нас функция, внутри которой мы можем использовать if, for и другие операторы.

    Чтобы вернуть значение, мы просто пишем return, так же, как в синхронной функции. Мы можем использовать try/catch внутри, и мы поймаем исключение.


    Если Promises с getUser разрешится с ошибкой, это выкинется как исключение.

    getUserName функция возвращает Promises, поэтому можем работать с ним так же, как с любым Promises, можем навешивать callback"и с помощью then, чейнить и т.д.

    Но, как я уже сказал, использовать генераторы для асинхронного кода - это хак. Поэтому экспоузить в качестве внешней API нежелательно. Но использовать внутри приложения нормально, так что пользуйтесь, если у вас есть возможность транспайлить ваш код.


    Есть множество реализаций. Какие-то используют генераторы, которые уже часть стандарта, есть Fibers, которые в Node.js работают и которые не используют генератор, а свои заморочки у них.

    В общем, это третий подход для работы с единичными асинхронными операциями, и это пока еще хак, но мы уже можем использовать код, который приближен к синхронному. Мы можем использовать условные операторы, циклы и блоки try/catch.


    Т.е. ECMAScript 6 для работы с единичными асинхронными операциями как бы немного приближает нас к желаемому результату, но по-прежнему проблема единого интерфейса не решена, даже в Coroutines, потому что нам нужно писать «*» специальную и использовать ключевой оператор «yield».


    Итак, в ECMAScript 6 для работы с множеством событий у нас есть EventEmitter и Stream, для работы с единичными асинхронными операциями - CPS, Promises, Coroutines. И все это, вроде бы, здорово, но чего-то не хватает. Хочется большего, чего-то отважного, смелого, нового, хочется революции.

    И ребята, которые пишут ES7, решили дать нам революцию и принесли для нас Async/Await и Async Generators.


    Async/Await - это стандарт, который позволяет нам работать с единичными асинхронными операциями, такими, как, например, запросы в БД.

    Вот так мы писали getUserName на генераторах:


    А вот так тот же код выглядит с помощью Async/Await:


    Все очень похоже, по большому счету, это шаг в сторону от хака к стандарту. Здесь у нас появилось ключевое слово «async», которое говорит о том, что функция асинхронная, и она вернет Promise. Внутри асинхронной функции мы можем использовать ключевое слово «await», там, где мы возвращаем Promise. И мы можем дожидаться выполнения этого Promise, мы можем ставить функцию на паузу и дожидаться выполнения этого Promise.


    И так же у нас работают условные операторы, циклы, и try/catch, то бишь, асинхронные функции легализованы в ES7. Теперь мы явно говорим, что если функция асинхронная, то добавьте ключевое слово «async». И это, в принципе, не так плохо, но опять же единого интерфейса у нас нет.

    Что по поводу множества событий? Здесь у нас есть стандарт, который называется Async Generators.

    Что такое, вообще, множество? Как мы работаем с множеством в JavaScript?


    C множеством мы работаем при помощи циклов, так давайте работать с множеством событий при помощи циклов.


    Внутри асинхронной функции мы можем использовать ключевую конструкцию «for… on», которая нам позволяет итерировать по асинхронным коллекциям. Как бы.

    В данном примере observe возвращает нам что-то, по чему мы можем итерировать, т.е. каждый раз, когда пользователь будет двигать мышкой, у нас будет появляться событие «mousemove». Мы попадаем в этот цикл, и обрабатываем как-то это событие. В данном случае рисуем черточки на экране.


    Т.к. функция асинхронная, важно понимать, что она возвращает Promise. Но что, если мы хотим вернуть множество значений, если мы хотим, например, обрабатывать как-то сообщения из веб-сокета, фильтровать их? Т.е. у нас поступает множество и на выходе у нас множество. Здесь нам помогают асинхронные генераторы. Мы пишем «async function *» и говорим о том, что функция асинхронна, и мы возвращаем множество какое-то.


    В данном случае мы смотрим на событие Message на веб-сокете и каждый раз, когда оно происходит, мы делаем какую-то проверку и, если проверка проходит, мы в возвращаемую коллекцию. Как бы добавляем этот Message.


    При чем, все это происходит асинхронно. Сообщения не копятся, они возвращаются по мере того, как приходят. И здесь так же работают все наши условные операторы, циклы и try/catch.

    Вопрос: что возвращает filterWSMessages?


    Это точно не Promise, потому что это какая-то коллекция, что-то в этом роде… Но это и не массив.


    Даже больше. Что возвращают эти Observe, которые генерят события?

    А возвращают они т.н. объекты Observables. Это новое слово, но по большому счету, Observables - это потоки, это Streams. Т.о., круг замыкается.

    Итого, у нас есть для работы с асинхронными единичными операциями Async/Await, для работы с множеством - Async Generators.

    Давайте пройдемся и сделаем небольшую ретроспективу того, от чего мы ушли и к чему пришли.

    Чтобы получить ленту твитов, в CPS мы писали бы вот такой код:


    Много бойлерплейта, обработка ошибок, ручная практически и, в общем, не очень приятная.

    С помощью Promise код выглядит таким вот образом:


    Бойлерплейт поменьше, мы можем обрабатывать исключения в одном месте, что уже хорошо, но, тем не менее, есть эти then..., не работают ни try/ catch, ни условные операторы.

    С помощью Async/Await получаем такую конструкцию:


    И примерно то же нам дают Coroutines.

    Здесь все шикарно, за исключением того, что нам нужно объявить эту функцию как «async».

    Что касается множества событий, если мы говорим о DOM-событиях, вот так мы обрабатывали бы mousemove и рисовали бы по экрану с помощью EventEmitter"а:


    Тот же самый код, но с помощью Stream"ов и библиотеки Kefir выглядит таким образом:


    Мы создаем поток из событий mousemove на window, мы их каким-то образом фильтруем, и на каждое значение мы вызываем callback функцию. И, когда мы вызовем end у этого stream"а, мы автоматически отпишемся от событий в DOM, что немаловажно

    Async Generators выглядит таким образом:


    Это просто цикл, мы итерируем по коллекции асинхронных событий и производим какие-то операции над ними.

    По-моему, это огромный путь.

    Хотелось бы в заключение сказать пару слов о том, как, собственно, перестать отлаживать асинхронный код и начать жить.

    • Определите вашу задачу, т.е. если вы работаете с множеством событий, имеет смысл посмотреть на Streams или, возможно даже, на Async Generators, если у вас есть транспайлер.
    • Если вы работаете с базой, например, отправляете туда запросы или отправляете AJAX-запросы, которые могут либо зафейлиться, либо выполниться, используйте Promises.
    • Обдумайте ваши ограничения. Если вы можете использовать транспайлер, имеет смысл посмотреть на Async/Await и Async Generators. Если вы пишете API, возможно, не имеет смысла экспоузить Promise в качестве внешней API и делать все на callback’ах.
    • Используйте лучшие практики, помните про события error на потоках и на EventEmitter"ах.
    • Помните про специальные методы вроде Promise.all и т.д.

    Я знаю, всех вас интересует судьба Schlecht!Script, когда он будет выложен на GitHub и т.д., но дело в том, что из-за постоянной критики, обвинений в плагиате - говорят, что язык такой уже есть, ничего нового я не изобрел, я решил закрыть проект и посвятить себя, может быть, чему-то полезному, важному, интересному, возможно, я даже напишу свою библиотеку Promises.