Если задачи получаются с шиной сложнее чем без шины, то, очевидно, шина не подходит для решения этого спектра задач, либо избыточно используется.
Описанные мною ситуации коварны тем что они возникают и сложны тогда, когда приложение уже развивается и рефакторится
При этом если каналы используют более чем одним подписчиком, то поиск решений в пределах уже используемой архитектуры подписок выглядит меньшим злом
кстати хорошие варианты дает комбинация шины (слабые связи) и простых способов (сильные связи).
Да, тоже полезный вариант
Более того, когда модуль-источник имеет сложное внутреннее состояние(и много каналов об изменении различных его частей), то в потребителе имеет смысл не обновлять свою копию этого состояния(тем самым дублируя логику источника, и поддерживая лишнюю копию состояния в памяти), а запрашивать его при каждом обновляющем событии.
При условии что запрос состояния обходится достаточно дешево.
Если запрос полного состояния обходится дорого, то можно ввести промежуточный кэширующий модуль, который и будет держать копию состояния в актуальном(и легко-доступном виде), и всем потребителям запрашивать данные из него(а то и вовсе подписаться на его канал(ы), которые могут быть более общими чем у источника)
Тогда логика поддержания копии(теперь уже кэша) состояния будет сосредоточена в одном модуле, а не размазана по всем потребителям
В большинстве случаев да, проще, но иногда возникают сложности из-за того что связность никуда не делась, а приобрела другую форму
Например:
1) модуль A посылает событие, которое слушают модули B и C.
При этом модуль C должен получить сообщение после модуля B
В некоторых библиотеках для решения такой задачи можно задавать приоритет при подписке
Также достаточно чистым вариантом будет учесть что на самом деле C зависит не только от A, но и от B, и бросать сообщение уже из B, но такому сообщению нужно внятное имя канала(не всегда очевидное), и это увеличивает количество связей(степеней свободы) между модулями.
Все становится гораздо грустнее если такая неявная связь образовалась из-за когда-то строгого порядка подписок(и не пришлось задавать приоритеты/создавать дополнительные каналы, ведь все работает), а в процессе рефакторинга этот приоритет поменялся(из-за его неочевидности)
2) модуль A зависит от данных из каналов X и Y — например составной виджет, не умеющий рисоваться по-частям.
При этом в разных ситуациях сообщения X и Y могут бросаться как подряд, так и по-одиночке(второе не бросается).
Можно в модуле A сделать некий дебаунсинг в ожидании второго сообщения(либо его отсутствия), но это не добавит отзывчивости приложению. Особенно если таких комплексных подписчиков будет много. А если они при этом еще и сами бросают события для других комплексных подписчиков, то для последнего в цепочке дебаунсинг может затянутся
Можно в модулях ответственных за каналы X и Y начать бросать их всегда, но в случае отсутствия новых данных бросать пустые(либо старые данные). Тогда модуль A всегда будет получать пару событий, и начинать работу после второго. Но если на эти каналы уже полно других подписчиков, то придется их научить «ничего не делать» на такие варианты событий, иначе будет беда с производительность
Да, такие ситуации могут возникать достаточно редко(исходя из моего опыта), но если уж они возникают то отлаживать и исправлять их достаточно трудоемко, особенно на этапе когда уже готова значительная часть приложения
Нет в этом мире совершенства и серебряной пули)
Отсутствие return означает возврат Promise в данном случае.
Таким образом в promises попадет массив промисов, которые зарезолвятся в undefined после окончания соответствующих sendRequest.
Promise.all дождется факта окончания всех операций, и вернет массив undefined-ов, вместо результатов.
Исходная задача(синхронные операции длительностью в 1 секунду) — уже не в концепции ноды
Это решается либо переносом в воркер, либо превращением синхронного кода в асинхронный.
В последнем случае, начиная с корневой функции вашего генератора превращаете ее в async-функцию, сдабривая await-ами nextTick-а и await-ами дочерних вызовов
Затем делаете тоже самое с дочерними функциями
Продолжаем до тех пор пока синхронный код между двумя await-ами не будет занимать приемлемое время(например меньше 50мс)
В итоге во время генерации нового файла предыдущие файлы прекрасно могут отдаваться
Более того, если генерацию запросили одновременно два клиента, то второй не будет ждать первого, а их файлы будут генериться параллельно(но дольше). Если нужно это предотвратить — оберните внешнюю функцию в однопоточный семафор
Размазывать права по всему коду приложения?
Более того если один внешний модуль подключается их двух точек вашего приложения? — Что произойдет если они укажут разные права?
Вариантом получше видится вызов библиотеки в одной точке приложения, и там же определить права для всех внешних модулей(ну или вообще в отдельном конфиг-файле)
Использую модуль webpack-config
Позволяет вынести общую часть в базовый конфиг, а в конкретных конфигах его расширять
У меня это расширение большей частью сводится к редактированию DefinePlugin
Кроме того можно настройки редко используемых(либо опциональных) плагинов вынести в отдельные файлы, и подключать/отключать одной строчкой
Например unused-files-webpack-plugin, webpack-bundle-analyzer для локальной работы/анализа
Настроенный UglifyJsPlugin только для серверных сборок
Как итог, в файле под каждое окружение находится его более конкретное описание, отличающее его от других конфигов, и при этом без if-ов в неожиданных местах
Я все еще не понимаю почему нельзя.
Есть webpack.
В предыдущей версии дополненный babel-ем прекрасно собирает нативные модули в один файл.
В webpack@2 вроде и без babel умеет.
Да, внутренняя кухня сложнее(трансформация кода вместо простой склейки).
Но результат впрочем тот же — собранный файл, с возможностью отделить необязательные модули в отдельный файл для ленивой загрузки.
Ну и бонусом куча дополнительных плюшек сборки.
да, подразумевались макрокоманды как в офисе, либо других прикладных программах.
но в javascript-е могут быть и макросы как в C
1) Вот плагин babel-а, в котором я участвую — https://github.com/codemix/babel-plugin-macros
но тут скорее о макросах, как об инлайновых функциях
2) Также есть отдельная надстройка синтаксиса — http://sweetjs.org/
тут наверно можно гораздо шире определить макросы
Но нормальные юзкейсы со строго последовательным асинхронными операциями также существуют:
1. Операция сейва в редакторе
— подготовка превью-картинки
тут может быть параллельно еще какая-то подготовка мета-данных
— упаковка в архив
— пересылка на сервер
— опционально показ красивого(не-alert) модального окна по завершении
2. массовые операция редактирования/анализа кучи файлов
в целом операции будут идти параллельно(либо в очереди с заданным лимитом прааллельности)
но для отдельного файла будет последовательный набор шагов
— прочитать файл
— провести изменение/анализ файла
— записать файл
3. система макросов для приложения.
так как некоторые пользовательские операции вынуждены быть асинхронными, то и система макросов становится асинхронными(но строго последовательными)
Вот тут из моей практики async..await превращает код вообще в песню, по-сравнению с колбеками, или даже чистыми промисами
Хотелось бы узнать больше об этих минных тропах.
с обычными callback-ами мне доводилось сталкиваться с тремя проблемными анти-патернами:
1) асинхронная функция содержит некоторое количество синхронного кода
если он упадет, асинхронная функция никогда не вернет результата.
весьма пакостный для отладки случай.
— чистые промисы решают эту проблему не полностью, если использовать их как попало
2) асинхронная функция делает что-то еще после вызова callback
— странно что-то делать после того как вернул результат.
— этим что-то может оказаться повторный вызов callback(например сразу после if-а), что приводит к двойному старту последующего кода
— но для этой ошибки есть правило в eslint)
— промисы решают эту проблему
3) асинхронная функция вызывает callback на самом деле синхронно(например данные из кэша)
— может порождать дивные стектрейсы в случае падения в колбеке(захватится уже «выполненная» функция не имеющая отношения к падению)
— при попытке решать первую проблему try..catch-ами, приводит ко второй проблеме — err-callback будет вызван еще раз
— логика сложнее в отладке, так как нарушен контракт
— промисы решают эту проблему
Использование async..await автоматически фиксит все 3 проблемы, даже не допуская возможности так ошибиться
И единственный встреченный минус: стектрейсы при отладке все еще не синхронные)
Ну и дополнены обертками от babel
Поэтому интересуют какие проблемы сохранили/создали async..await-ы?
Особенно в контексте того что, в C# есть способы ранней проверки анти-патернов.
Я бы с удовольствием написал бы пару правил для статического анализа в eslint
Это скорее способ, чтобы асинхронный(но последовательный) код выглядел почти как синхронный линейный код
Отличие только в том, что внутренние асинхронные вызовы нужно предварять await-ом
При этом базовая функция приостановит свое выполнение(но запомнит состояние локальных переменных и стек), и освободит поток для других функций.
Когда же внутренний вызов завершится, исходная функция продолжит свое выполнение с запомненной точки
И самое приятное, что любое исключение(хоть синхронное, хоть асинхронное) может быть одинаково поймано с помощью try..catch
Используя транспайлеры(например babel) вполне поддерживается, причем практически в любом окружении.
Более того, с помощью babel-а, можно использовать и кучу других синтаксических плюшек из будущих стандартов и даже то, что не планируется к стандартизации в языке(например react, flow)
Пожалуйста поясните как именно Babel вытесняет jQuery?
Вроде бы совершенно разные вещи с совершенно разными задачами, и я не представляю какие именно задачи, решаемые jQuery, может выполнять Babel (и похоже делать это лучше)
Да нет же, это две независимых вещи
typescript, насколько я понимаю язык, в котором синтаксис типов является хоть и центральной, но далеко не единственной синтаксической фичей
flowtype же, это инструмент статического анализа, который позволяет проверять типы, и на этом все
При этом я полагаю что делает он эту задачу лучше(во всяком случае в первом примере для flowtype, typescript не видит никаких проблем)
И да, к слову, flowtype все-таки позволяет описывать типы в комментариях.
То есть вы все-таки можете использовать типы, и при этом писать «чистый» JS.
Правда сам синтаксис остается принципиально тем же(просто экранирован комментариями), так что в этом мало смысла, если вы используете IDE, знающую о flowtype — поэтому я не вижу смысла в этой «чистоте».
Но это скорее дело личного вкуса.
Это плагин к babel, который автоматически добавляет проверки на тип, если вы описали тип.
Для описания типов используется flow type (http://flowtype.org/) синтаксис.
плюсы:
+ возможность проверять не только входящие аргументы, но и локальные переменные.
+ не нужна принудительная верблюжья нотация для описания типов — для описания типов используется отдельный синтаксис.
+ не нужно как-то особо описывать используемые вами классы
плагин самостоятельно сгенерит typeof/instanceof-проверки, если вы задали настоящий JS-тип для переменной
+ скорость в рантайме. Не приходится использовать Proxy, а сами проверки встраиваются инлайново, а не вызовами функций.
+ оттранслированный код работает в любом окружении, а не только там, где есть Proxy.
минусы:
— не всем может понравится синтасис описания типов в коде.
Это уже не чистый JS, хотя и поддерживаемый некоторыми IDE
Описанные мною ситуации коварны тем что они возникают и сложны тогда, когда приложение уже развивается и рефакторится
При этом если каналы используют более чем одним подписчиком, то поиск решений в пределах уже используемой архитектуры подписок выглядит меньшим злом
Да, тоже полезный вариант
Более того, когда модуль-источник имеет сложное внутреннее состояние(и много каналов об изменении различных его частей), то в потребителе имеет смысл не обновлять свою копию этого состояния(тем самым дублируя логику источника, и поддерживая лишнюю копию состояния в памяти), а запрашивать его при каждом обновляющем событии.
При условии что запрос состояния обходится достаточно дешево.
Если запрос полного состояния обходится дорого, то можно ввести промежуточный кэширующий модуль, который и будет держать копию состояния в актуальном(и легко-доступном виде), и всем потребителям запрашивать данные из него(а то и вовсе подписаться на его канал(ы), которые могут быть более общими чем у источника)
Тогда логика поддержания копии(теперь уже кэша) состояния будет сосредоточена в одном модуле, а не размазана по всем потребителям
Например:
1) модуль A посылает событие, которое слушают модули B и C.
При этом модуль C должен получить сообщение после модуля B
В некоторых библиотеках для решения такой задачи можно задавать приоритет при подписке
Также достаточно чистым вариантом будет учесть что на самом деле C зависит не только от A, но и от B, и бросать сообщение уже из B, но такому сообщению нужно внятное имя канала(не всегда очевидное), и это увеличивает количество связей(степеней свободы) между модулями.
Все становится гораздо грустнее если такая неявная связь образовалась из-за когда-то строгого порядка подписок(и не пришлось задавать приоритеты/создавать дополнительные каналы, ведь все работает), а в процессе рефакторинга этот приоритет поменялся(из-за его неочевидности)
2) модуль A зависит от данных из каналов X и Y — например составной виджет, не умеющий рисоваться по-частям.
При этом в разных ситуациях сообщения X и Y могут бросаться как подряд, так и по-одиночке(второе не бросается).
Можно в модуле A сделать некий дебаунсинг в ожидании второго сообщения(либо его отсутствия), но это не добавит отзывчивости приложению. Особенно если таких комплексных подписчиков будет много. А если они при этом еще и сами бросают события для других комплексных подписчиков, то для последнего в цепочке дебаунсинг может затянутся
Можно в модулях ответственных за каналы X и Y начать бросать их всегда, но в случае отсутствия новых данных бросать пустые(либо старые данные). Тогда модуль A всегда будет получать пару событий, и начинать работу после второго. Но если на эти каналы уже полно других подписчиков, то придется их научить «ничего не делать» на такие варианты событий, иначе будет беда с производительность
Да, такие ситуации могут возникать достаточно редко(исходя из моего опыта), но если уж они возникают то отлаживать и исправлять их достаточно трудоемко, особенно на этапе когда уже готова значительная часть приложения
Нет в этом мире совершенства и серебряной пули)
Таким образом в promises попадет массив промисов, которые зарезолвятся в undefined после окончания соответствующих sendRequest.
Promise.all дождется факта окончания всех операций, и вернет массив undefined-ов, вместо результатов.
Это решается либо переносом в воркер, либо превращением синхронного кода в асинхронный.
В последнем случае, начиная с корневой функции вашего генератора превращаете ее в async-функцию, сдабривая await-ами nextTick-а и await-ами дочерних вызовов
Затем делаете тоже самое с дочерними функциями
Продолжаем до тех пор пока синхронный код между двумя await-ами не будет занимать приемлемое время(например меньше 50мс)
В итоге во время генерации нового файла предыдущие файлы прекрасно могут отдаваться
Более того, если генерацию запросили одновременно два клиента, то второй не будет ждать первого, а их файлы будут генериться параллельно(но дольше). Если нужно это предотвратить — оберните внешнюю функцию в однопоточный семафор
Более того если один внешний модуль подключается их двух точек вашего приложения? — Что произойдет если они укажут разные права?
Вариантом получше видится вызов библиотеки в одной точке приложения, и там же определить права для всех внешних модулей(ну или вообще в отдельном конфиг-файле)
Позволяет вынести общую часть в базовый конфиг, а в конкретных конфигах его расширять
У меня это расширение большей частью сводится к редактированию DefinePlugin
Кроме того можно настройки редко используемых(либо опциональных) плагинов вынести в отдельные файлы, и подключать/отключать одной строчкой
Например unused-files-webpack-plugin, webpack-bundle-analyzer для локальной работы/анализа
Настроенный UglifyJsPlugin только для серверных сборок
Как итог, в файле под каждое окружение находится его более конкретное описание, отличающее его от других конфигов, и при этом без if-ов в неожиданных местах
Есть webpack.
В предыдущей версии дополненный babel-ем прекрасно собирает нативные модули в один файл.
В webpack@2 вроде и без babel умеет.
Да, внутренняя кухня сложнее(трансформация кода вместо простой склейки).
Но результат впрочем тот же — собранный файл, с возможностью отделить необязательные модули в отдельный файл для ленивой загрузки.
Ну и бонусом куча дополнительных плюшек сборки.
А то гугл выдает ссылку на ваш же коммент.
но в javascript-е могут быть и макросы как в C
1) Вот плагин babel-а, в котором я участвую — https://github.com/codemix/babel-plugin-macros
но тут скорее о макросах, как об инлайновых функциях
2) Также есть отдельная надстройка синтаксиса — http://sweetjs.org/
тут наверно можно гораздо шире определить макросы
Но нормальные юзкейсы со строго последовательным асинхронными операциями также существуют:
1. Операция сейва в редакторе
— подготовка превью-картинки
тут может быть параллельно еще какая-то подготовка мета-данных
— упаковка в архив
— пересылка на сервер
— опционально показ красивого(не-alert) модального окна по завершении
2. массовые операция редактирования/анализа кучи файлов
в целом операции будут идти параллельно(либо в очереди с заданным лимитом прааллельности)
но для отдельного файла будет последовательный набор шагов
— прочитать файл
— провести изменение/анализ файла
— записать файл
3. система макросов для приложения.
так как некоторые пользовательские операции вынуждены быть асинхронными, то и система макросов становится асинхронными(но строго последовательными)
Вот тут из моей практики async..await превращает код вообще в песню, по-сравнению с колбеками, или даже чистыми промисами
с обычными callback-ами мне доводилось сталкиваться с тремя проблемными анти-патернами:
1) асинхронная функция содержит некоторое количество синхронного кода
если он упадет, асинхронная функция никогда не вернет результата.
весьма пакостный для отладки случай.
— чистые промисы решают эту проблему не полностью, если использовать их как попало
2) асинхронная функция делает что-то еще после вызова callback
— странно что-то делать после того как вернул результат.
— этим что-то может оказаться повторный вызов callback(например сразу после if-а), что приводит к двойному старту последующего кода
— но для этой ошибки есть правило в eslint)
— промисы решают эту проблему
3) асинхронная функция вызывает callback на самом деле синхронно(например данные из кэша)
— может порождать дивные стектрейсы в случае падения в колбеке(захватится уже «выполненная» функция не имеющая отношения к падению)
— при попытке решать первую проблему try..catch-ами, приводит ко второй проблеме — err-callback будет вызван еще раз
— логика сложнее в отладке, так как нарушен контракт
— промисы решают эту проблему
Использование async..await автоматически фиксит все 3 проблемы, даже не допуская возможности так ошибиться
И единственный встреченный минус: стектрейсы при отладке все еще не синхронные)
Ну и дополнены обертками от babel
Поэтому интересуют какие проблемы сохранили/создали async..await-ы?
Особенно в контексте того что, в C# есть способы ранней проверки анти-патернов.
Я бы с удовольствием написал бы пару правил для статического анализа в eslint
Отличие только в том, что внутренние асинхронные вызовы нужно предварять await-ом
При этом базовая функция приостановит свое выполнение(но запомнит состояние локальных переменных и стек), и освободит поток для других функций.
Когда же внутренний вызов завершится, исходная функция продолжит свое выполнение с запомненной точки
И самое приятное, что любое исключение(хоть синхронное, хоть асинхронное) может быть одинаково поймано с помощью try..catch
Хочется упомянуть близкий по задачам плагин — babel-plugin-macros
Позволяет объявить любую функцию с меткой macro:, и она будет инлайново развернута в месте вызова.
Более того, с помощью babel-а, можно использовать и кучу других синтаксических плюшек из будущих стандартов и даже то, что не планируется к стандартизации в языке(например react, flow)
Вроде бы совершенно разные вещи с совершенно разными задачами, и я не представляю какие именно задачи, решаемые jQuery, может выполнять Babel (и похоже делать это лучше)
typescript, насколько я понимаю язык, в котором синтаксис типов является хоть и центральной, но далеко не единственной синтаксической фичей
flowtype же, это инструмент статического анализа, который позволяет проверять типы, и на этом все
При этом я полагаю что делает он эту задачу лучше(во всяком случае в первом примере для flowtype, typescript не видит никаких проблем)
И да, к слову, flowtype все-таки позволяет описывать типы в комментариях.
То есть вы все-таки можете использовать типы, и при этом писать «чистый» JS.
Правда сам синтаксис остается принципиально тем же(просто экранирован комментариями), так что в этом мало смысла, если вы используете IDE, знающую о flowtype — поэтому я не вижу смысла в этой «чистоте».
Но это скорее дело личного вкуса.
Из известного мне — github.com/codemix/babel-plugin-typecheck.
Это плагин к babel, который автоматически добавляет проверки на тип, если вы описали тип.
Для описания типов используется flow type (http://flowtype.org/) синтаксис.
плюсы:
+ возможность проверять не только входящие аргументы, но и локальные переменные.
+ не нужна принудительная верблюжья нотация для описания типов — для описания типов используется отдельный синтаксис.
+ не нужно как-то особо описывать используемые вами классы
плагин самостоятельно сгенерит typeof/instanceof-проверки, если вы задали настоящий JS-тип для переменной
+ скорость в рантайме. Не приходится использовать Proxy, а сами проверки встраиваются инлайново, а не вызовами функций.
+ оттранслированный код работает в любом окружении, а не только там, где есть Proxy.
минусы:
— не всем может понравится синтасис описания типов в коде.
Это уже не чистый JS, хотя и поддерживаемый некоторыми IDE