Андрей Саломатин ( filipovskii_off )
![Андрей Саломатин](https://habrastorage.org/getpro/habr/post_images/3c5/792/17f/3c579217f44dd53f3f002800db6eec29.jpg)
Сегодня каждый день появляются новые языки программирования — Go, Rust, CoffeeScript — все, что угодно. Я решил, что я тоже горазд придумать свой язык программирования, что миру не хватает какого-то нового языка…
Дамы и господа, я представляю вам сегодня Schlecht!Script — чумовой язык программирования. Мы все должны начать им пользоваться прямо сейчас. В нем есть все то, к чему мы привыкли — в нем есть условные операторы, есть циклы, есть функции и функции высших порядков. В общем, в нем есть все, что нужно нормальному языку программирования.
Что в нем не очень обычно, что может даже оттолкнуть, на первый взгляд, — это то, что в Schlecht!Script функции имеют цвет.
![](https://habrastorage.org/getpro/habr/post_images/218/8a9/f44/2188a9f446f005b40c0ca60f02cc8ad0.png)
Т.е., когда вы объявляете функцию, когда вы ее вызываете, вы явным образом указываете ее цвет.
Функции бывают красные и синие — двух цветов.
Важный момент: внутри синих функций вы можете вызывать только другие синие функции. Вы не можете вызывать красные функции внутри синих функций.
![](https://habrastorage.org/getpro/habr/post_images/2da/3f1/4f7/2da3f14f7a4fd2ca0263d83de3213a98.png)
Внутри красных функций вы можете вызывать и красные, и синие функции.
![](https://habrastorage.org/getpro/habr/post_images/de1/4bb/868/de14bb8682222237d754c2936fc7f041.png)
Я решил, что должно быть так. В каждом языке должно быть так.
Тонкий момент: красные функции писать и вызывать больно! Что я имею в виду, когда говорю «больно»? Дело в том, что сейчас я изучаю немецкий язык, и я решил, что красные функции мы должны все называть только на немецком языке, иначе интерпретатор просто не поймет, что вы ему пытаетесь впихнуть, и он просто не будет это выполнять.
![](https://habrastorage.org/getpro/habr/post_images/22d/2fb/9ed/22d2fb9edd17b716a87f67a98f6214a0.png)
Вот так вы должны писать функции на немецком языке:
![](https://habrastorage.org/getpro/habr/post_images/bd7/3d5/28d/bd73d528d75186b66be37c841d0bf417.png)
«!» обязателен — мы же на немецком пишем, в конце концов.
Как писать на таком языке? У нас есть два способа. Мы можем использовать только синие функции, в которые писать не больно, но внутри мы не можем пользоваться красными функциями. Этот подход не будет работать, потому что в порыве вдохновения я написал половину стандартной библиотеки на красных функциях, так что, простите…
Вопрос к вам — стали бы вы использовать такой язык? Продал ли я вам Schlecht!Script?
Ну, у вас, как бы, нет выбора. Простите…
JavaScript.
JavaScript — отличный язык, мы все его любим, мы все здесь собрались, потому что мы любим JavaScript. Но проблема в том, что JavaScript наследует некоторые черты Schlecht!Script, и я, конечно, не хочу хвастаться, но, по-моему, они украли пару моих идей.
Что именно они наследуют? в JavaScript есть красные и синие функции. Красные функции в JavaScript — это асинхронные функции, синие — синхронные функции. И все прослеживается, все та же цепочка… Красные функции вызывать больно в Schlecht!Script, а асинхронные функции вызывать больно в JavaScript.
И внутри синих функций мы не можем писать красные функции. Я еще скажу об этом позже.
![](https://habrastorage.org/getpro/habr/post_images/5f9/7d9/b51/5f97d9b518254247033060371a4aa63c.png)
Почему это больно? Откуда боль при вызове и при написании асинхронных функций?
У нас по-другому работают условные операторы, циклы, return. У нас не работает try/catch, и асинхронные функции ломают абстракцию.
О каждом пункте немного подробнее.
![](https://habrastorage.org/getpro/habr/post_images/9ae/1fd/d36/9ae1fdd368c4f5a4af2102b265c72444.png)
Вот так выглядит синхронный код, где shouldProcess и process — это функции синхронные, и работают условные операторы, работает for, в общем, все хорошо.
То же самое, но асинхронное, будет выглядеть вот так вот:
![](https://habrastorage.org/getpro/habr/post_images/fa2/294/a99/fa2294a9997aabd3eb8da5ef8ac5c0b9.png)
Там появилась рекурсия, мы передаем состояние в параметры, в функцию. В общем, смотреть прямо-таки неприятно. У нас не работает try/catch и, я думаю, мы все знаем, что если мы обернем асинхронный блок кода в try/catch, исключения мы не поймаем. Нам нужно будет передать callback, перенавесить обработчик событий, в общем, у нас нет try/catch…
И асинхронные функции ломают абстракцию. Что я имею в виду? Представьте, что вы написали кэш. Вы сделали кэш пользователей в памяти. И у вас есть функция, которая читает из этого кэша, которая, естественно, синхронная, потому что все в памяти. Завтра к вам приходят тысячи, миллионы, миллиарды пользователей, и вам нужно положить этот кэш в Redis. Вы кладете кэш в Redis, функция становится асинхронной, потому что из-за Redis'а мы можем читать только асинхронно. И, соответственно, весь стек, который вызывал вашу синхронную функцию, придется переписать, потому что теперь весь стек становится асинхронным. Если какая-то функция зависела от функции чтения из кэша, она так же будет теперь асинхронной.
В общем и целом, говоря об асинхронности в JavaScript, можно сказать, что все там грустно.
Но что мы все об асинхронности? Давайте немного обо мне поговорим, наконец.
![](https://habrastorage.org/getpro/habr/post_images/571/8b9/4b9/5718b94b9c13be7f9aed6e58e3b689e2.png)
Я пришел, чтобы вас всех спасти. Ну, я попробую это сделать.
Меня зовут Андрей, я работаю в стартапе «Productive Mobile» в Берлине. Я помогаю с организацией MoscowJS и я являюсь соведущим RadioJS. Мне очень интересна тема асинхронности и не только в JavaScript, я считаю, что, в принципе, это определяющий момент языка. То, как язык работает с асинхронностью, определяет его успех и то, насколько людям приятно и удобно с ним работать.
Говоря об асинхронности конкретно в JavaScript, мне кажется, у нас есть два сценария, с которыми мы постоянно взаимодействуем. Это обработка множества событий и обработка единичных асинхронных операций.
Множество событий — это, например, событие DOM или подключение к серверу — что-то, что излучает множество нескольких типов событий.
Единичная операция — это, к примеру, чтение из базы. Единичная асинхронная операция возвращает нам либо один результат, либо возвращает ошибку. Больше никаких вариантов нет.
И, говоря об этих двух сценариях, интересно порассуждать: вот, типа, асинхронность — плохо, в общем, все грустно… А что мы на самом деле хотим? Как бы выглядел идеальный асинхронный код?
![](https://habrastorage.org/getpro/habr/post_images/86c/635/ae6/86c635ae6c736459ae82435ce4225d23.png)
А хотим мы, мне кажется, контроля потока управления. Мы хотим, чтобы наши условные операторы, циклы работали в синхронном коде так же, как в асинхронном.
Мы хотим обработки исключений. Зачем нам try/catch, если мы не можем его использовать в асинхронных операциях? Это просто странно.
И желательно, конечно, иметь единый интерфейс. Почему асинхронная функция должна писаться и вызываться по-другому, по сравнению с синхронной? Этого не должно быть.
Вот, чего мы хотим.
А что у нас есть сегодня, и какие инструменты у нас появятся в будущем?
![](https://habrastorage.org/getpro/habr/post_images/221/f2d/e14/221f2de14ce1e75a51203151970e3cc5.png)
Если мы говорим об ECMAScript 6 (это, в принципе, то, о чем я буду говорить сегодня), для работы с множеством событий у нас есть EventEmitter и Stream, а для работы с единичными асинхронными операциями — Continuation Passing Style (они же callback'и), Promises и Coroutines.
![](https://habrastorage.org/getpro/habr/post_images/909/895/e02/909895e02837a921318eb704eac8179e.png)
В ECMAScript 7 у нас появятся Async Generators для работы с множеством событий и Async/Await — для работы с единичными асинхронными операциями.
Об этом и поговорим.
Начнем с того, что у нас есть в ECMAScript 6 для работы с множеством асинхронных событий. Напомню, например, это обработка событий мыши или нажатий на клавиши. У нас есть паттерн EventEmitter, который реализован в браузере в Node.js. Он встречается практически в любой API, где мы работаем с множеством событий. EventEmitter говорит нам, что мы можем создать объект, который излучает события, и навешивать обработчики на каждый тип события.
![](https://habrastorage.org/getpro/habr/post_images/34f/9b8/1f1/34f9b81f147c41756ce21942afda3245.png)
Интерфейс очень простой. Мы можем добавлять EventListener, убирать EventListener по названию event'а, передавая туда сallback.
![](https://habrastorage.org/getpro/habr/post_images/f3e/e89/8b9/f3ee898b984c23e0a89bb3a64cd18e45.png)
К примеру, в XMLHttpRequest, когда я говорю о множестве событий, я имею в виду, что у нас может быть множество событий progress. Т.е. по мере того, как мы загружаем какие-то данные с помощью AJAX-запроса, нам выстреливают события progress, и по одному разу выстреливают события load, abort и error:
![](https://habrastorage.org/getpro/habr/post_images/1b6/457/e06/1b6457e064e88520978f1f829c03eaa4.png)
Error — это особенное событие, универсальное событие в EventEmitter'ах и в Stream'ах для того чтобы уведомить пользователя об ошибке.
Есть множество реализаций:
![](https://habrastorage.org/getpro/habr/post_images/eef/7d0/df3/eef7d0df31b5f18c638463176c6db549.png)
Здесь перечислены только несколько, и в конце доклада будет ссылка, где все эти реализации есть.
Важно сказать, что в Node.js EventEmitter встроен по умолчанию.
Итак, это то, что у нас есть практически по стандарту в API и браузерах в Node.js.
Что у нас еще есть для работы с множеством событий? Stream.
Stream — это поток данных. Что такое данные? Это могут быть бинарные данные, например, данные из файла, текстовые данные, либо объекты или события. Самые популярные примеры:
![](https://habrastorage.org/getpro/habr/post_images/6d4/1bc/2af/6d41bc2af575f144fd561119330219e5.png)
Есть несколько типов потоков:
![](https://habrastorage.org/getpro/habr/post_images/fdd/d72/4a2/fddd724a24063fa26b2c8a6c45a4043c.png)
Здесь мы рассматриваем цепочку преобразований из Stylus файлов в css файлы, добавляя автопрефиксер, потому что все мы любим Андрея Ситника и его автопрефиксер.
Вы видите, что у нас есть несколько типов потоков — поток-источник это gulp.src, который читает файлы и излучает объекты файлы, которые потом идут в потоки преобразования. Первый поток преобразования делает из stylus файла css, второй поток преобразования добавляет префиксы. И последний тип потоков — это поток-потребитель, который принимает эти объекты, пишет что-то куда-то на диск, и ничего не излучает.
![](https://habrastorage.org/getpro/habr/post_images/2df/f02/a76/2dff02a76d0ceb7020a4e823455ccb48.png)
Т.е. у нас есть 3 типа потоков — источник данных, преобразование и потребитель. И эти паттерны прослеживаются везде, не только в gulp, но и при наблюдении за DOM событиями. У нас есть потоки, которые излучают DOM-события, которые их преобразуют и что-то, что потребляя эти DOM-события, возвращает конкретный результат.
Это то, что можно назвать конвейером. С помощью потоков мы можем выстраивать такие цепочки, когда объект кладется куда-то в начало конвейера, проходит цепочку преобразований и, когда к нему подходят люди, что-то там меняют, добавляют, удаляют, и в конечном итоге у нас выходит какой-нибудь автомобиль.
Есть несколько реализаций потоков, или они же Observables:
![](https://habrastorage.org/getpro/habr/post_images/a28/c81/d19/a28c81d197202f4b5497b8b668d08fd3.png)
В Node.js потоки встроенные — это Node Streams.
Итак, у нас есть EventEmitter и Stream. EventEmitter по умолчанию есть во всех API, а. Stream — это надстройка, которую мы можем использовать для того, чтобы унифицировать интерфейс обработки множества событий.
![](https://habrastorage.org/getpro/habr/post_images/673/073/2d6/6730732d6f2e375c232d78dff09cf31a.png)
Когда мы говорим о тех критериях, по которым мы сравниваем асинхронные API, у нас не работают, по большому счету, операторы return, операторы-циклы, у нас не работают try/catch, естественно, и до единого интерфейса с синхронными операциями нам еще далеко.
В общем, для работы с множеством событий в ECMAScript 6 все не очень хорошо.
Когда мы говорим об единичных асинхронных операциях, у нас есть 3 подхода в ECMAScript 6:
![](https://habrastorage.org/getpro/habr/post_images/a7c/bbd/2c3/a7cbbd2c38cb577a4c12a10a9c389344.png)
Continuation Passing Style, они же — callback'и.
![](https://habrastorage.org/getpro/habr/post_images/0ae/358/fb5/0ae358fb5768b38885ff9db40c82584e.png)
Я думаю, вы все к этому уже привыкли. Это когда мы делаем асинхронный запрос, передаем туда callback, и callback будет вызван либо с ошибкой, либо с результатом. Это распространенный подход, он есть и в браузере в Node.
![](https://habrastorage.org/getpro/habr/post_images/ca6/e16/b23/ca6e16b23df6ac778531dae274ec6f20.png)
Проблемы с этим подходом, я думаю, вы тоже все понимаете.
![](https://habrastorage.org/getpro/habr/post_images/ecd/ceb/513/ecdceb513cde73c1c2b1cf71387bbcc7.png)
Вот так бы мы получали ленту твитов пользователей асинхронно, если бы все функции были синхронными.
![](https://habrastorage.org/getpro/habr/post_images/966/a46/dd5/966a46dd58f0527047dee967c110338f.png)
Тот же самый код, но синхронно, выглядит следующим образом:
![](https://habrastorage.org/getpro/habr/post_images/2aa/633/4fc/2aa6334fc1f9ae335d524e9b447e7135.png)
Можно увеличить шрифт, чтобы было виднее. И еще немного увеличить… И мы прекрасно понимаем, что это Schlecht!Script.
![](https://habrastorage.org/getpro/habr/post_images/3e6/da2/8ec/3e6da28ecbae853f6608314023cb2d60.png)
Я говорил, что они украли мою идею.
Continuation Passing Style — это стандартный API в браузере в Node.js, мы работаем с ними постоянно, но это неудобно. Поэтому у нас есть Promises. Это объект, который представляет собой асинхронную операцию.
![](https://habrastorage.org/getpro/habr/post_images/06e/373/93a/06e37393a308f3d2f70fd9ee9f57b14a.png)
Promise — это как бы обещание что-то сделать в будущем, асинхронно.
![](https://habrastorage.org/getpro/habr/post_images/c9b/8ff/2f2/c9b8ff2f2ce2613d2c712b1340ffde37.png)
Мы можем навешивать 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'и.
![](https://habrastorage.org/getpro/habr/post_images/755/32a/89a/75532a89a2f64f69dc8fdedd4a8fca50.png)
Реализаций Promises — масса, и если вы не написали свою реализацию, вы ненастоящий JavaScript-программист. Я не написал свою реализацию Promises, поэтому мне пришлось придумать свой язык.
Итак, Promises, в общем, чуть меньше бойлерплейта, но, тем не менее, все еще не так хорошо.
Что по поводу Coroutines? Здесь уже начинаются интересные штуки. Представьте…
Это интересно. Мы были на JSConf в Будапеште, и там был сумасшедший человек, который на JavaScript программировал квадракоптер и что-то еще, и половину из того, что он пытался нам показать, у него не получалось. Поэтому он постоянно говорил: «OK, теперь представьте… Этот квадракоптер взлетел и все получилось...».
Представьте, что мы можем поставить функцию на паузу в какой-то момент ее выполнения.
![](https://habrastorage.org/getpro/habr/post_images/069/dba/e16/069dbae163a8ab7627a933afc7356564.png)
Здесь функция получает имя пользователя, она лезет в базу, получает объект пользователя, возвращает его имя. Естественно, «залезть в базу» — функция getUser асинхронная. Что, если мы могли бы поставить функцию getUserName на паузу в момент вызова getUser? Вот, мы выполняем нашу getUserName функцию, дошли до getUser и остановились. getUser сходил в базу, получил объект, вернул его в функцию, мы продолжаем выполнение. Насколько круто это было бы.
Дело в том, что Coroutines дают нам эту возможность. Coroutines — это функция, которую мы можем приостанавливать и возобновлять в любой момент времени. Важный момент: мы не останавливаем всю программу.
![](https://habrastorage.org/getpro/habr/post_images/9f1/a50/706/9f1a507067ff914ee936b173bca95f1a.png)
Это не блокирующая операция. Мы останавливаем выполнение конкретной функции в конкретном месте.
![](https://habrastorage.org/getpro/habr/post_images/4b4/086/4b2/4b40864b27da22f263cfd0d6b8b87cab.png)
Как getUserName выглядит с помощью генераторов на JavaScript? Нам нужно добавить «*» в объявление функции, чтобы сказать о том, что функция возвращает генератор. Мы можем использовать ключевое слово «yield» в том месте, где мы хотим поставить функцию на паузу. И важно помнить, что getUser здесь возвращает Promises.
Т.к. генераторы изначально придумали для того чтобы делать ленивые последовательности в JavaScript, по большому счету, использовать их для синхронного кода — это хак. Поэтому нам нужны библиотеки, чтобы как-то это компенсировать.
![](https://habrastorage.org/getpro/habr/post_images/1ba/2f5/43d/1ba2f543d9d839d54cba762f95bba59c.png)
Здесь мы используем «co» для того, чтобы обернуть генератор и вернуть нам асинхронную функцию.
Итого, вот, что у нас получается:
![](https://habrastorage.org/getpro/habr/post_images/6e4/2fa/67a/6e42fa67a948388f052c16cc8f49ebb6.png)
У нас функция, внутри которой мы можем использовать if, for и другие операторы.
Чтобы вернуть значение, мы просто пишем return, так же, как в синхронной функции. Мы можем использовать try/catch внутри, и мы поймаем исключение.
![](https://habrastorage.org/getpro/habr/post_images/8aa/c78/546/8aac785462b1b0d0648da7c676cce870.png)
Если Promises с getUser разрешится с ошибкой, это выкинется как исключение.
getUserName функция возвращает Promises, поэтому можем работать с ним так же, как с любым Promises, можем навешивать callback'и с помощью then, чейнить и т.д.
Но, как я уже сказал, использовать генераторы для асинхронного кода — это хак. Поэтому экспоузить в качестве внешней API нежелательно. Но использовать внутри приложения нормально, так что пользуйтесь, если у вас есть возможность транспайлить ваш код.
![](https://habrastorage.org/getpro/habr/post_images/28d/45f/ef4/28d45fef486a925ed70d454a027f5e33.png)
Есть множество реализаций. Какие-то используют генераторы, которые уже часть стандарта, есть Fibers, которые в Node.js работают и которые не используют генератор, а свои заморочки у них.
В общем, это третий подход для работы с единичными асинхронными операциями, и это пока еще хак, но мы уже можем использовать код, который приближен к синхронному. Мы можем использовать условные операторы, циклы и блоки try/catch.
![](https://habrastorage.org/getpro/habr/post_images/f98/20d/e10/f9820de1041f8164fe5074f901dec39a.png)
Т.е. ECMAScript 6 для работы с единичными асинхронными операциями как бы немного приближает нас к желаемому результату, но по-прежнему проблема единого интерфейса не решена, даже в Coroutines, потому что нам нужно писать «*» специальную и использовать ключевой оператор «yield».
![](https://habrastorage.org/getpro/habr/post_images/749/daa/42d/749daa42dfd16ec7f869035d3984ad5c.png)
Итак, в ECMAScript 6 для работы с множеством событий у нас есть EventEmitter и Stream, для работы с единичными асинхронными операциями — CPS, Promises, Coroutines. И все это, вроде бы, здорово, но чего-то не хватает. Хочется большего, чего-то отважного, смелого, нового, хочется революции.
И ребята, которые пишут ES7, решили дать нам революцию и принесли для нас Async/Await и Async Generators.
![](https://habrastorage.org/getpro/habr/post_images/53a/1f8/1c9/53a1f81c93e944362cf486315b0a7fca.png)
Async/Await — это стандарт, который позволяет нам работать с единичными асинхронными операциями, такими, как, например, запросы в БД.
Вот так мы писали getUserName на генераторах:
![](https://habrastorage.org/getpro/habr/post_images/937/394/24d/93739424dd4786bce5fad82c8ba34af4.png)
А вот так тот же код выглядит с помощью Async/Await:
![](https://habrastorage.org/getpro/habr/post_images/944/ea9/496/944ea94967db91a9849ac8ce5fb0d356.png)
Все очень похоже, по большому счету, это шаг в сторону от хака к стандарту. Здесь у нас появилось ключевое слово «async», которое говорит о том, что функция асинхронная, и она вернет Promise. Внутри асинхронной функции мы можем использовать ключевое слово «await», там, где мы возвращаем Promise. И мы можем дожидаться выполнения этого Promise, мы можем ставить функцию на паузу и дожидаться выполнения этого Promise.
![](https://habrastorage.org/getpro/habr/post_images/1cb/b63/7fb/1cbb637fbc474e77857a872e264bafd6.png)
И так же у нас работают условные операторы, циклы, и try/catch, то бишь, асинхронные функции легализованы в ES7. Теперь мы явно говорим, что если функция асинхронная, то добавьте ключевое слово «async». И это, в принципе, не так плохо, но опять же единого интерфейса у нас нет.
Что по поводу множества событий? Здесь у нас есть стандарт, который называется Async Generators.
Что такое, вообще, множество? Как мы работаем с множеством в JavaScript?
![](https://habrastorage.org/getpro/habr/post_images/d34/285/cec/d34285cec15e988a5e6f055ae1266ccf.png)
C множеством мы работаем при помощи циклов, так давайте работать с множеством событий при помощи циклов.
![](https://habrastorage.org/getpro/habr/post_images/34c/c25/a6f/34cc25a6fb7f75620b426f235439eb09.png)
Внутри асинхронной функции мы можем использовать ключевую конструкцию «for… on», которая нам позволяет итерировать по асинхронным коллекциям. Как бы.
В данном примере observe возвращает нам что-то, по чему мы можем итерировать, т.е. каждый раз, когда пользователь будет двигать мышкой, у нас будет появляться событие «mousemove». Мы попадаем в этот цикл, и обрабатываем как-то это событие. В данном случае рисуем черточки на экране.
![](https://habrastorage.org/getpro/habr/post_images/eb3/42a/a65/eb342aa6538ccf591b04881333ef20ac.png)
Т.к. функция асинхронная, важно понимать, что она возвращает Promise. Но что, если мы хотим вернуть множество значений, если мы хотим, например, обрабатывать как-то сообщения из веб-сокета, фильтровать их? Т.е. у нас поступает множество и на выходе у нас множество. Здесь нам помогают асинхронные генераторы. Мы пишем «async function *» и говорим о том, что функция асинхронна, и мы возвращаем множество какое-то.
![](https://habrastorage.org/getpro/habr/post_images/31c/793/d9c/31c793d9ce6d6d50db6d2109d842c99d.png)
В данном случае мы смотрим на событие Message на веб-сокете и каждый раз, когда оно происходит, мы делаем какую-то проверку и, если проверка проходит, мы в возвращаемую коллекцию. Как бы добавляем этот Message.
![](https://habrastorage.org/getpro/habr/post_images/a26/37a/83c/a2637a83c617c766d7730d5d26e5f0d9.png)
При чем, все это происходит асинхронно. Сообщения не копятся, они возвращаются по мере того, как приходят. И здесь так же работают все наши условные операторы, циклы и try/catch.
Вопрос: что возвращает filterWSMessages?
![](https://habrastorage.org/getpro/habr/post_images/59b/a09/c45/59ba09c45503c70a66a42d70a4736c7e.png)
Это точно не Promise, потому что это какая-то коллекция, что-то в этом роде… Но это и не массив.
![](https://habrastorage.org/getpro/habr/post_images/517/049/508/517049508a69598e42e1a25f31925402.png)
Даже больше. Что возвращают эти Observe, которые генерят события?
А возвращают они т.н. объекты Observables. Это новое слово, но по большому счету, Observables — это потоки, это Streams. Т.о., круг замыкается.
Итого, у нас есть для работы с асинхронными единичными операциями Async/Await, для работы с множеством — Async Generators.
Давайте пройдемся и сделаем небольшую ретроспективу того, от чего мы ушли и к чему пришли.
Чтобы получить ленту твитов, в CPS мы писали бы вот такой код:
![](https://habrastorage.org/getpro/habr/post_images/f45/ea7/8d1/f45ea78d12f11e7a12f9ab62eb51fb8e.png)
Много бойлерплейта, обработка ошибок, ручная практически и, в общем, не очень приятная.
С помощью Promise код выглядит таким вот образом:
![](https://habrastorage.org/getpro/habr/post_images/0d7/af1/66d/0d7af166d49c1c22741b4414e629656d.png)
Бойлерплейт поменьше, мы можем обрабатывать исключения в одном месте, что уже хорошо, но, тем не менее, есть эти then..., не работают ни try/ catch, ни условные операторы.
С помощью Async/Await получаем такую конструкцию:
![](https://habrastorage.org/getpro/habr/post_images/412/144/8f5/4121448f537d2305c231aafd9df52385.png)
И примерно то же нам дают Coroutines.
Здесь все шикарно, за исключением того, что нам нужно объявить эту функцию как «async».
Что касается множества событий, если мы говорим о DOM-событиях, вот так мы обрабатывали бы mousemove и рисовали бы по экрану с помощью EventEmitter'а:
![](https://habrastorage.org/getpro/habr/post_images/7a7/586/2fd/7a75862fd1b3ebb8d9267e0cdb9e94fc.png)
Тот же самый код, но с помощью Stream'ов и библиотеки Kefir выглядит таким образом:
![](https://habrastorage.org/getpro/habr/post_images/c71/b89/7f2/c71b897f2757b736c024813704f9a633.png)
Мы создаем поток из событий mousemove на window, мы их каким-то образом фильтруем, и на каждое значение мы вызываем callback функцию. И, когда мы вызовем end у этого stream'а, мы автоматически отпишемся от событий в DOM, что немаловажно
Async Generators выглядит таким образом:
![](https://habrastorage.org/getpro/habr/post_images/9f3/552/fda/9f3552fdac63a49ca68facb7a26673c3.png)
Это просто цикл, мы итерируем по коллекции асинхронных событий и производим какие-то операции над ними.
По-моему, это огромный путь.
Хотелось бы в заключение сказать пару слов о том, как, собственно, перестать отлаживать асинхронный код и начать жить.
- Определите вашу задачу, т.е. если вы работаете с множеством событий, имеет смысл посмотреть на Streams или, возможно даже, на Async Generators, если у вас есть транспайлер.
- Если вы работаете с базой, например, отправляете туда запросы или отправляете AJAX-запросы, которые могут либо зафейлиться, либо выполниться, используйте Promises.
- Обдумайте ваши ограничения. Если вы можете использовать транспайлер, имеет смысл посмотреть на Async/Await и Async Generators. Если вы пишете API, возможно, не имеет смысла экспоузить Promise в качестве внешней API и делать все на callback’ах.
- Используйте лучшие практики, помните про события error на потоках и на EventEmitter'ах.
- Помните про специальные методы вроде Promise.all и т.д.
Я знаю, всех вас интересует судьба Schlecht!Script, когда он будет выложен на GitHub и т.д., но дело в том, что из-за постоянной критики, обвинений в плагиате — говорят, что язык такой уже есть, ничего нового я не изобрел, я решил закрыть проект и посвятить себя, может быть, чему-то полезному, важному, интересному, возможно, я даже напишу свою библиотеку Promises.
Контакты
» filipovskii_off
Этот доклад — расшифровка одного из лучших выступлений на конференции фронтенд-разработчиков FrontendConf. Мы уже открыли приём докладов на 2017 год.
На главной странице Frontend Conf, кстати, есть форма подписки на лучшие материалы конференции. Подпишитесь, минимум 8 лучших расшифровок, гарантированы.
А на ближайшей HighLoad++ уже 11 заявок по теме «Производительность фронтенда», например:
- Применяем стандарты кодирования NASA к JavaScript / Денис Радин (Liberty Global);
- Отрисовать за 16 мс / Глеб Михеев (Beta Digital Production);
- Порядок для скорости. Система структурирования фронтендовой части веб-приложений / Сергей Гущин (SuperJob);