Създавайте наблюдаеми от различни типове данни, обекти и събития

Въведение

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

С оглед на това реших да създам поредицата „RxJS Lessons“, която ще обхване всеки отделен оператор, предоставен от RxJS с прости примери. Ето частите, от които се състои тази поредица досега:

  • RxJS уроци: Как да създаваме наблюдаеми

Как да създадете наблюдаеми

RxJS ни предоставя много функции за създаване на Observables. Всъщност толкова много, че някои разработчици могат да се окажат в затруднение откъде да започнат. В тази част от поредицата „RxJS Lessons“ ще разгледаме всички функции за създаване на Observable, които са следните:

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

Без повече шум, нека да започнем:

Функции за създаване

‘of()’

of() ни позволява да създаваме Observables от масиви, обекти, низове и т.н. Той ще излъчва всеки получен аргумент като цяло и след това ще го завърши. Нека да разгледаме как да го използваме:

Тук имаме четири различни наблюдаеми, създадени с 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() излъчва аргумента като цяло. Какво означава това? Ще го разберем веднага щом се абонираме за няколко Observables, създадени с from(). Така че първо, нека създадем някои наблюдаеми:

Както можете да видите, ние създаваме пет различни наблюдаеми в горния кодов фрагмент:

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

Нека се абонираме и да разгледаме резултата от тези наблюдаеми:

Време е да играете да откриете разликата. Нека сравним и двете fruit$ наблюдаеми, които създадохме от масив от низове с помощта на функциите from() и of():

Виждаш ли разликата? Сега можем да наблюдаваме, че from() излъчва елементите, които са вътре в масива, докато of() излъчва целия масив. Можем да кажем, че from() има ефект на изравняване: взема масив, низ или обект и излъчва елементите, които те съдържат.

Можете да си поиграете с from() в този StackBlitz.

„FromEvent()“

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

  • Целта на събитието, към която ще бъде прикачена функцията за обработка на събития
  • Типът събитие, което искаме да слушаме

Ще създадем четири различни наблюдаеми от четири различни събития: click, keydown, scroll и copy:

Ако някое от четирите събития, които използвахме, се задейства от потребителски взаимодействия с документа, съответният Observable ще се излъчва. Каква ще бъде излъчената стойност? Нека да разгледаме изхода:

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

Но как нашите Observables могат да слушат целите на събитието? Не трябва ли да добавяме манипулатори на събития?

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

Можете да си поиграете с fromEvent() в този StackBlitz.

„интервал()“

interval() създава Observable, който излъчва поредица от възходящи числа въз основа на предоставен интервал от време. Параметърът за размер на времеви интервал не е задължителен със стойност по подразбиране 0. Трябва да сме много внимателни с това. С това казано, нека да разгледаме няколко примера:

Както можете да оцените, interval() е много проста функция. Единственото нещо, с което трябва да внимаваме, е стойността на интервала по подразбиране е 0.

Можете да си поиграете с interval() в този StackBlitz.

„таймер()“

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

  • Първоначалното забавяне за изчакване преди излъчване на първата стойност
  • Стойността на интервала между емисиите

И двата параметъра са незадължителни, със стойност по подразбиране 0.

Нека да разгледаме някои примери:

Можете да си поиграете с timer() в този StackBlitz.

"обхват()"

range() създава Observable, който излъчва диапазон от нарастващи последователни числа. Получава два параметъра:

  • Началната точка на диапазона
  • Дължината на диапазона - известен още като количеството числа, които ще бъдат излъчени. Този параметър е незадължителен със стойност по подразбиране 0.

Ето един пример:

Можете да си поиграете с range() в този StackBlitz.

„отлагане()“

defer() ни позволява да създаваме Observables лениво. Той изчаква, докато се абонираме, и след това изпълнява логиката за създаване на Observable.

Това означава, че нов Observable се създава от всеки абонамент. Това може да звучи малко объркващо, но е много лесно за разбиране, ако разгледаме пример, сравняващ Observable, създаден с defer(), и Observable, създаден с друга функция за създаване.

Нека създадем Observable с of():

Както можете да видите в примера, имаме функция getRandomPokemon(), която, както показва името й, връща произволен покемон. Създадохме randomPokemon$ Observable от функцията getRandomPokemon(), използвайки of(). Въпреки това, когато се абонираме за този Observable, винаги получаваме същата стойност: Charmander. Не трябва ли да получаваме произволен покемон всеки път, когато се абонираме? Отговорът е не. Защо?

Тъй като функцията getRandomPokemon() се изпълнява, когато се създава Observable, а не когато се абонираме. Това означава, че се изпълнява само веднъж – когато създаваме Observable. Без значение колко пъти се абонираме, Observable вече е създаден, така че винаги получаваме едни и същи Pokémon.

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

Създадохме randomPokemonForReal$ Observable с помощта на defer() от функцията getRandomPokemon(). Както можете да видите в горния пример, всеки път, когато се абонираме за него, получаваме различен Pokémon. Както казахме преди, това е така, защото логиката за създаване на Observable (в този случай функцията getRandomPokemon()) се изпълнява всеки път, когато се абонираме.

За да обобщим, можем да използваме defer(), когато имаме нужда от мързеливи наблюдаеми. Можете да си поиграете с 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 Lessons“.

Надявам се, че този урок ви е харесал и ви е бил полезен. наздраве