Создавайте Observables из различных типов данных, объектов и событий.

Вступление

RxJS - самый популярный фреймворк для функционального реактивного программирования (FRP) на JavaScript. Angular полностью построен на концепциях RxJS и FRP. Это фантастическая технология и невероятно полезная, но у нее есть один серьезный недостаток: у нее довольно крутая кривая обучения, что разочаровывает некоторых разработчиков.

В связи с этим я решил создать серию «Уроки RxJS», которые охватят каждый оператор, предоставляемый RxJS, простыми примерами. Вот части, из которых на данный момент состоит эта серия:

  • Уроки RxJS: как создавать наблюдаемые

Как создавать наблюдаемые

RxJS предоставляет нам множество функций для создания Observables. На самом деле так много, что некоторые разработчики не могут понять, с чего начать. В этой части серии «Уроки RxJS» мы рассмотрим все функции создания Observable, а именно:

  • of()
  • from()
  • fromEvent()
  • interval()
  • timer()
  • range()
  • defer()
  • ajax()
  • fromFetch()
  • generate()

Без лишних слов, приступим:

Функции создания

‘of()’

of() позволяет нам создавать Observables из массивов, объектов, строк и т. Д. Он генерирует каждый аргумент, который он получает, как единое целое, а затем завершает. Давайте посмотрим, как его использовать:

Здесь у нас есть четыре разных Observable, созданных с помощью of():

  • number$ Observable, созданный из последовательности чисел
  • pokemon$ Observable, созданный из трех строк
  • fruit$ Observable, созданный из двух массивов строк
  • iceCream$ Observable, созданный из двух объектов

Итак, как мы можем получить доступ к значениям, которые будут излучать наши Observables? Подписавшись на них, вот так:

Как видите, мы можем подписаться на Observable, чтобы получить доступ к генерируемым им значениям.

Совет: мы можем избежать использования анонимной функции (функция =>), если захотим, и сделаем вместо этого следующее:

pokemon$.subscribe(console.log);
// Output: Squirtle Charmander Bulbasur

Я считаю, что гораздо легче понять Observables и то, как они работают, если вы поиграетесь с ними и увидите их результат в реальном времени, поэтому я подготовил StackBlitz для каждого фрагмента кода, который мы рассмотрим в этом руководстве. Поэтому я рекомендую вам поиграть с of() в этом StackBlitz.

'из()'

from() создает Observables из массива, обещания, объекта, строки и т. Д. Звучит of() довольно знакомо, не так ли? Так в чем же между ними разница?

Разница в том, что from() испускает элементы, которые находятся внутри аргумента, который он получает, а of() испускает аргумент в целом. Что это значит? Мы поймем это, как только подпишемся на несколько Observable, созданных с помощью from(). Итак, сначала давайте создадим несколько Observables:

Как видите, в приведенном выше фрагменте кода мы создаем пять разных Observable:

  • letters$ Observable, созданный из строки
  • fruit$ Observable, созданный из массива строк
  • pokemon$ Observable, созданный из объекта Map
  • promise$ Observable, созданный из Promise
  • node$ Observable, созданный из NodeList

Давайте подпишемся и посмотрим на вывод этих Observables:

Пришло время поиграть в разницу. Давайте сравним оба fruit$ Observables, которые мы создали из массива строк с помощью функций from() и of():

Вы видите разницу? Теперь мы можем заметить, что from() испускает элементы, находящиеся внутри массива, а of() испускает весь массив. Можно сказать, что from() имеет эффект сглаживания: он принимает массив, строку или объект и испускает элементы, которые они содержат.

Вы можете поиграть с from() в этом StackBlitz.

‘FromEvent ()’

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

  • Цель события, к которой будет прикреплена функция обработчика событий.
  • Тип события, которое мы хотим слушать

Мы собираемся создать четыре разных Observable из четырех разных событий: click, keydown, scroll и copy:

Если любое из четырех использованных нами событий запускается взаимодействием пользователя с документом, соответствующий Observable сгенерирует. Какая будет выпущенная стоимость? Давайте посмотрим на результат:

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

Но как наши Observables могут прослушивать цели событий? Разве нам не нужно добавлять обработчики событий?

Что ж, в этом вся прелесть fromEvent(). Когда мы подписываемся на Observable, созданный с помощью fromEvent(), функция обработчика событий автоматически присоединяется к предоставленной цели события. Вот как наш Observable слушает, запускаются ли события или нет. Как только мы откажемся от подписки на любые Observable, созданные с помощью fromEvent(), Observable позаботится об удалении обработчиков событий за нас. Довольно круто, правда?

Вы можете поиграть с fromEvent() в этом StackBlitz.

‘Interval ()’

interval() создает Observable, который испускает последовательность возрастающих чисел на основе заданного временного интервала. Параметр размера временного интервала является необязательным и имеет значение по умолчанию 0. Мы должны быть очень осторожны с этим. С учетом сказанного, давайте рассмотрим несколько примеров:

Как вы понимаете, interval() - очень простая функция. Единственное, с чем нужно быть осторожным, - это значение интервала по умолчанию 0.

Вы можете поиграть с interval() в этом StackBlitz.

«Таймер ()»

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

  • Начальная задержка ожидания перед выдачей первого значения
  • Величина интервала между выбросами

Оба параметра являются необязательными, их значение по умолчанию - 0.

Давайте посмотрим на несколько примеров:

Вы можете поиграть с timer() в этом StackBlitz.

'диапазон()'

range() создает Observable, который испускает диапазон возрастающих последовательных чисел. Он получает два параметра:

  • Отправная точка диапазона
  • Длина диапазона - или количество чисел, которые будут выданы. Этот параметр является необязательным, его значение по умолчанию - 0.

Вот пример:

Вы можете поиграть с range() в этом StackBlitz.

‘Defer ()’

defer() позволяет нам лениво создавать Observables. Он ждет, пока мы подпишемся, а затем выполняет логику создания Observable.

Это означает, что каждая подписка создает новый Observable. Это может показаться немного запутанным, но это очень легко понять, если мы рассмотрим пример, сравнивающий Observable, созданный с помощью defer(), и Observable, созданный с помощью какой-либо другой функции создания.

Давайте создадим Observable с of():

Как вы можете видеть в примере, у нас есть функция getRandomPokemon(), которая, как видно из ее названия, возвращает случайного покемона. Мы создали randomPokemon$ Observable из функции getRandomPokemon(), используя of(). Однако, когда мы подписываемся на этот Observable, мы всегда получаем одно и то же значение: Charmander. Разве мы не должны получать случайных покемонов при каждой подписке? Ответ - нет. Почему?

Потому что функция getRandomPokemon() выполняется при создании Observable, а не при подписке. Это означает, что он выполняется только один раз - когда мы создаем Observable. Независимо от того, сколько раз мы подписываемся, Observable уже создан, поэтому мы всегда получаем одного и того же покемона.

По сути, мы трижды подписываемся на один и тот же Observable, который выдает Charmander. Итак, как мы можем заставить функцию getRandomPokemon() выполняться каждый раз, когда мы подписываемся, вместо того, чтобы выполняться при создании Observable? Используя defer():

Мы создали randomPokemonForReal$ Observable, используя defer() из функции getRandomPokemon(). Как вы можете видеть в приведенном выше примере, каждый раз, когда мы подписываемся на него, мы получаем другого покемона. Как мы уже говорили ранее, это связано с тем, что логика создания Observable (в данном случае функция getRandomPokemon()) выполняется каждый раз, когда мы подписываемся.

Подводя итог, мы можем использовать defer(), когда нам нужны ленивые Observables. Вы можете поиграть с defer() в этом StackBlitz.

‘Ajax ()’

Как видно из названия, функция ajax() создает Observable из запроса AJAX. Давайте посмотрим, как его использовать:

Примечание. Мы будем использовать Ghibli API для тестирования наших вызовов AJAX.

Когда мы создаем Observable с ajax(), он испускает весь объект AjaxResponse, возвращенный из вызова API. Но что, если нас интересуют только возвращенные данные? Оказывается, у ajax() есть небольшая изящная функция под названием getJSON():

Отлично! Мы получили необходимые данные. Однако возникает другой вопрос: что, если нам нужно установить собственные заголовки? Нет проблем, мы можем установить их, передав объект ajax():

И вуаля! Теперь у нас есть Observable, созданный с ajax(), с настраиваемыми заголовками. Вы можете поиграть с ajax() в этом StackBlitz.

‘FromFetch ()’

Запросы AJAX подходят, но что, если мы захотим использовать вместо них fetch? Нет проблем, fromFetch() позволяет нам это делать. Давайте посмотрим на пример:

Как видите, как и в случае с ajax(), Observable возвращает весь объект Response. Так что, если нам просто нужны данные? В отличие от ajax(), fromFetch() не имеет getJSON() функции. Итак, как мы можем получить данные? Сделав что-то вроде этого:

А как насчет обработки ошибок - ведь с HTTP-запросами очень многое может пойти не так? Вот пример того, как обрабатывать ошибки:

Я знаю, что это довольно много кода, поэтому давайте разберем его:

  • Сначала мы используем оператор switchMap() и проверяем, был ли ответ правильным. Используя условие защиты, мы выдаем ошибку, если ответ был неверным. Если ничего не пошло не так, мы возвращаем содержимое тела JSON из ответа с помощью функции json().
  • Затем мы используем оператор catchError(), чтобы перехватить ошибку (в случае ее возникновения) и распечатать ее. Затем мы возвращаем Observable объекта, содержащего сообщение об ошибке и свойство ошибки, равное true.
  • Напоследок подписываемся. Если вызов fetch прошел успешно, мы получим данные ответа. В противном случае мы получим объект ошибки с сообщением о том, что пошло не так.

Вы можете поиграть с fromFetch() в этом StackBlitz.

‘Generate ()’

Я хочу посвятить generate() всем любителям цикла for. generate() создаст наблюдаемую последовательность, запустив управляемый состоянием цикл, который создаст элементы в последовательности. Он получает три разных параметра:

  • initialState: определяет начальную точку цикла, начальное значение
  • condition: функция, определяющая условие завершения цикла. Он получает текущее значение и проверяет, выполняется ли указанное условие. Если это так, цикл продолжается. В противном случае цикл заканчивается.
  • iterate: функция, которая определяет, как определенное значение будет изменяться на каждой итерации.

Это может показаться немного сложным, но это действительно легко понять, как только вы это увидите:

Первый параметр - это значение initialState. Как видите, первое значение нашего цикла будет 1.

Второй параметр - это функция условия - наш цикл будет продолжаться, пока текущее значение меньше 10.

Наконец, третий параметр соответствует тому, как текущее значение будет изменяться при каждом взаимодействии. В этом случае он будет увеличен на 1, что означает, что наш number$ Observable будет выдавать числа от 1 до 9.

Однако передача анонимных функций в generate() выглядит немного странно, не так ли? Вместо этого мы можем передать объект, если предпочитаем такой синтаксис. Вот пример.

Как видите, наш evenNumber$ Observable выдаст все четные числа от 2 до 10.

Предупреждение. Передача объекта в generate() позволяет нам опустить переменную условия. Если мы сделаем это, мы создадим бесконечный цикл, поэтому будьте очень осторожны.

Вот пример бесконечного цикла. Не делайте этого:

Вы можете поиграть с generate() в этом StackBlitz.

Заключение

Вот и все, ребята! Мы рассмотрели каждую функцию, предоставляемую RxJS для создания Observable. Как вы могли видеть, существует множество способов создания Observables, хотя не все из них обычно используются.

Знание того, как создать Observable, необходимо для начала нашего путешествия по RxJS, но настоящая сила RxJS заключается в манипулировании потоком с помощью конвейерных операторов. Мы рассмотрим все существующие операторы RxJS в следующих руководствах из серии «Уроки RxJS».

Надеюсь, вам понравился этот урок, и вы сочли его полезным. Ваше здоровье.