100 операторов RxJs

Трудно быть стартапом. Часть 2 — учёба.

100 операторов RxJs

Трудно быть стартапом. Часть 2 — учёба.

Оружейник Гаук демонстрирует работу книгопечатной машины. Кадр из фильма “Трудно быть богом”

Введение

Итак, настало время второй статьи цикла “Трудно быть стартапом”. Предыдущая часть завершена призывом бесплатно делиться знаниями. Сам предложил — сам сделал. Позади примерно 40 часов труда, впереди — неувядаемая слава и почёт труженника. Поехали!

Для торопливых сразу ссылка на код библиотеки операторов, а для самых торопливых — ссылка на облачный сервис с развёрнутым кодом.

Для тех, кто ещё остался, кратенько попытаюсь обосновать зачем я потратил своё время. Итак, вначале был гугл. Можно долго и увлекательно бултыхаться в пёстрых статьях, но чем дальше заплываешь, тем больше тонешь.

Чёткое и выверенное изложение материала, как правило, доступно за деньги. А кроме того, обёрнуто увесистой программой обучения для увеличения стоимости. И при этом никто не даёт гарантий того, что это поможет решить ваши задачи. Ну то есть обещают, но на самом деле — нет. Ведь никто не знает какие конкретно задачи вам предстоит решить ;).

Вот мы и пришли к задаче. Она простая — надо приспособить код фронта к генератору случайных контрактов апи. Т.е. крепко изучить комбинацию потоков наблюдаемых значений —Observables. По пути, конечно, эти значения надо обложить обработчиками ошибок, интерфейсами, тестами, заглушками, авторизацией, нарезкой для листалки, и, конечно же, классной крутилкой “ваше мнение очень важно для нас”. И это не считая линтеров, соглашений по коду, архитектуры модулей и сервисов. Забегая вперёд, хочу отметить — мой стек Angular/TypeScript, потому техническая часть будет именно оттуда.

В основе этого “простейшего” действия — получить данные с сервера лежат операторы RxJS. Их задача облегчить разработку и сопровождение нашего кода, но у них есть и “тёмная” сторона. Операторов много, очень-очень много. И вариантов их использования, и параметров, и особенностей исполнения тоже очень много.

При попытке решить практическую задачу находишь пример. Он, конечно же, написан с использованием техник и операторов, которые не знакомы, а зачастую с ошибками. Ну или код примера банально устарел, и в текущей версии RxJS не работает. Да, более старые, “накликанные” и “нацитированные” годами ссылки будут всегда верхними в выдаче. И тогда начинается сборка коллекции кодовелосипедов в “свободное” время.

После укрепляющих лоб прыжков по граблям, у меня устоялся подход написания базы знаний и библиотек кода. Т.е. подготовки примеров кода ДО того момента, когда они понадобятся, а главное — сохранения полезного кода, на который я безвозвратно потратил своё время. Вот это и есть та самая библиотека, мой увесистый гранитный “кирпич” знаний.

Исходя из практического опыта родился лейтмотив библиотеки примеров: они должны быть максимально похожи друг на друга, чтобы их можно было быстро понимать и комбинировать. В практических задачах именно комбинация операторов отнимает больше всего времени. Их подбор и отладка — вот это вот то, ради чего написано 100+ примеров.

Полезные ссылки

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

Список того, к чему вы сами можете приложить руку(TODO)

  • заполнить для всех примеров объект IRunListItem: запуск, результат, тэги, описание, аналоги
  • сделать конфигуратор операторов: тип входных значений, выходных, преобразования, аналоги
  • заменить жаргонизмы на точное обозначение “подписывается, имитирует, …”(рытьё исходников)
  • добавить ссылки на https://www.learnrxjs.io/ и https://rxmarbles.com/ в описания всех операторов
  • привести примеры multicasting к массовому варианту запуска в index.ts
  • заполнить примерами новый файл testing.ts
  • написать тесты для для всех примеров
  • добавить в описания операторов их более простые аналоги/комбинации

Автоматическая проверка кода

По ходу дела я прикрутил в проект два линтера и несколько наборов правил.

  • сорян, но табы. Они позволяют настраивать каждому своё отображение, не меняя код в репе.
  • именование файлов в шашлычном стиле
  • именование интерфейсов с префиксом I
  • многие правила es/ts lint дублируются, часть отключено в одном из двух, но большинство оставлено, т.к. непонятно как конкретно работают правила, и непонятно что лучше.
  • правила форматирования переведены в severity: warn.
  • нельзя оставлять в коде console.log()
  • Финская нотация. Да, она через линтер помогает, например, не проглядеть тип значений внутри операторов map(item=>item... | map(item$=>item$... , т.к. вместо объекта может прилететь Observable, и это не всегда отлавливается tslint.
  • перелопатил все правила eslint, и добавил то, что добавилось. Искал правило для сложного случая, который периодически трепал мне нервы. Не нашёл :(.
  • rxjs-tslint-rules
  • codelyzer for Angular
  • angular-tslint-rules: a configuration preset for both TSLint & codelyzer
  • RxJS: Avoiding takeUntil Leaks
  • Best practices for a clean and performant Angular application

Как использовать библиотеку

  • можно открыть в IDE, а можно через chrome в облаке stackblitz
  • Необходимые операторы ищутся ctrl+f, в конце добавляем $ к названию оператора. Например switchMap$. Также операторы видны в "структуре кода" - специальном окне IDE.
  • Перед каждым примером есть небольшое описание и результат выполнения
  • чтобы заглушить ненужный входной поток достаточно дописать в начале *.pipe(* оператор take(0)

В облаке stackblitz:

  • обновить страницу
  • раскомментировать *$.subscribe(* строку необходимого оператора
  • открыть консоль встроенного браузера

Локально в IDE:

git clone https://github.com/bskydive/rxjs-aj4vwd-stackblitz.git
cd rxjs-aj4vwd-stackblitz
npm i
npm run b

Список плагинов VSCode, которые относятся к теме:

Что хорошо

  • Содержит полный список правильных способов import {}. Вроде бы мелочь, но автоимпорт не всегда работает корректно.
  • входные значения всегда потоки с интервалами, изредка — простые значения. Это имитирует боевые условия. Во многих примерах даются of(1,2,3), которые работают совсем иначе, чем interval(). Например, в примерах со switchMap — простые значения не дают понять, что предыдущий поток может быть закрыт. src/transforming.ts:881(switchMap3$)
  • Выводится ожидаемое время имитации значения в потоке interval(101).pipe(map(item=>item*101)).
  • Интервалы имитации разведены на милисекунду: 101, 102, 202, 203. Всегда понятно когда и в каком порядке должно быть имитировано значение.
  • К значениям из одного потока добавляются унифицированные постфиксы ‘-1’ | ‘-2’ | ‘-dynamic’, чтобы легче было читать вывод в консоли.
  • В примерах оставлены закоментированные операторы логирования для отладки tap(logAll)
  • Строка mergeMapSrc2$.subscribe((item) => logAll('получил: ', item), null, () => logAll('mergeMap2 поток закрыт')); унифицирована для облегчения рефакторинга
  • операторы endWith(‘…’) помогают понять когда происходит завершение(отписка) потока
  • выполняется как в консоли, так и в онлайн редакторе. Некоторые примеры работают только в браузере, когда необходимо его API, например, src/timing.ts:144(observeOn$) или src/filtering.ts:225(takeUntil$)
  • большое, очень большое количество операторов. 101 оператор разобран в 108 примерах
  • все примеры рабочие и готовы к копипасту
  • примеры многопоточные

Группировка операторов

  • buffering.ts — Операторы буферизации buffer*, window*
  • erroring.ts — Операторы обработки ошибок
  • filtering.ts — Операторы фильтрации
  • grouping.ts — Операторы группировки потоков и значений
  • multicasting.ts — Операторы асинхронного запуска потоков(распыления)
  • testing.ts — Операторы тестирования. Не реализовано.
  • timing.ts — Операторы времени, продолжительности и значений
  • tooling.ts — Операторы вспоможения в трудах
  • transforming.ts — Операторы трансформации потоков и значений
  • utils.ts — Интерфейсы и служебные функции

Что плохо

  • Не весь код универсально-одинаковый. Самый неодинаковый в примерах распыления multicasting.ts. Также в некоторых местах необходимо делать JSON.stringify в типовых *$.subscribe(*
  • Объём работы конский, потому, извиняйте, не всё сделано идеально. Было несколько подходов к рефакторингу, после которых я не выверял заново все примеры. Да, они рабочие, но вывод может отличаться от написанного в JSDoc
  • Описания операторов в тексте могут быть не вполне корректны. Главное здесь — рабочий код, а не его описание. Чтобы выверить описание необходимо в разы больше примеров, и перелопачивание исходников.

Типовой пример

Типовой пример описания оператора RxJs

Это был не только конструктор, но и пример создания таких конструкторов. Полезных себе и другим. Кроме того, здесь есть что ещё поделать, это хорошая основа для крутых учебников, конфигураторов или библиотек с примерами.
Впереди более сложный вид конструктора — приложение с библиотекой компонентов и хранилищем ngrx. Для затравки — годная статья