Мързелив, съставим и модулен JavaScript
Нека се съсредоточим върху използването на четири функции на ECMAScript: итератори, генератори, функции със стрелки и for-of оператор в комбинация с функции от по-висок ред, композиция на функции и мързелива оценка.
Преди да се потопим стремглаво в примера, нека разгледаме някои общи концепции.
Функции от по-висок ред
Функция от по-висок ред е функция, която отговаря на поне едно от следните условия:
- приема една или повече функции като аргументи;
- връща функция като резултат.
Вероятно сте попадали на функции от по-висок ред, ако сте написали манипулатор на събития или сте използвали Array.prototype.map().
Например, функция, влизайки в Array.prototype.map, не знае нищо за своята структура и методи. Единственото знание е механизмът за обработка на входящите данни, поради което може да се прилага многократно както за отделни стойности, така и за колекции.
Функционални композиции
Функционалната композиция е прилагането на една функция към резултата от друга. В резултат на това сложните функции се образуват от прости функции.
Например, има две функции: f и g. Резултатът от композицията (f, g) е функцията f(g(x)), която също може да се използва в композицията или да се предаде като параметър на функция от по-висок ред.
Да предположим, че ни е дадена задача да напишем програма, която приема файл като вход и връща масив, съдържащ броя на гласните във всяка дума на всеки ред. Едно решение е да се напише една голяма функция, която прави това.
Монолитното решение по-долу използва нов оператор for-of вместо обичайния for-цикъл за итериране на стойностите на масив. Итераторът е контейнер, който прилага протокола за последователна итерация и връща с помощта на операторадават стойности една по една (масиви, низове, генератори и т.н.).
Това решение не е разширяемо, не е мащабируемо и не съдържа компоненти за многократна употреба. Алтернативен подход е да се използват функции и композиция от по-висок ред.
listOfWordsByLine връща масив от масиви, където всеки елемент съответства на масив от думи, съставляващи низа. Например:
В примера vowelCount брои броя на гласните в една дума, vowelOccurrences използва vowelCount в изхода на listOfWordsByLine, за да преброи гласните във всеки ред.
Ползата от втория метод са универсални функции, които ще бъдат полезни в бъдещата работа и ще бъдат комбинирани заедно за решаване на големи проблеми.
По този начин те водят до подход отдолу нагоре, което води до код, който е композируем и модулен.
Мързелива оценка
Отложените („мързеливи”) изчисления са операции, чието изпълнение е отложено, докато резултатът е необходим.
Помислете за примера на "мързелива" обработка на данни и изграждане на "мързеливи" вериги от изчисления (тръбопроводи).
Даден е списък от цели числа. Необходимо е елементите на списъка да се повдигнат на квадрат и да се отпечата сумата на първите четири получени стойности.
За да напишем "мързелива" реализация, ще разберем кога е необходимо да извършим изчисления. Когато сумираме първите четири квадрата, тези елементи трябва да бъдат повдигнати на квадрат, така че ще отложим операцията за повдигане на квадрат, докато започне сумирането.
Повдигаме елементите на степен едва когато започне сумирането. Поради контрола на итерацията и добива се обработват само тези елементи, които ще участват в резултата. Итерацията се изпълнява по такъв начин, че елементите се връщат с помощта на отчета за доходност един по един, докато се получи сигналът за отсъствие.елементи за извеждане. Протоколът е капсулиран в обект на итератор, който съдържа една функция, next, която приема нулеви стойности. Следващият елемент се връща само ако има елементи.
Функцията squareAndSum приема итератор и n (броя на елементите в сумата) като вход. Чрез извикване на метода .next() той получава n стойности от итератора n пъти, поставя на квадрат всеки от елементите и ги сумира.
GetIterator връща итератор, образуван от нашия списък.
squareAndSumFirst4 използва getIterator и squareAndSum, за да върне сумата от първите четири числа във входния списък, повдигнати на квадрат по мързелив начин. Използването на итератори ви позволява да вграждате структури от данни, които могат да връщат безкрайни стойности, като използвате изявлението за доходност.
Трябва да правим горните стъпки всеки път, когато имаме нужда от итератор, затруднява писането на код. За щастие ES, от версия 6, предлага лесен начин за описание на итератори - генератори.
Генераторите поддържат и двата протокола, така че можете да получите стойности, като използвате оператора for-of.
Сега нека реализираме задача, която ще покаже как тези три подхода помагат за почистване на кода на приложението.
Пример: проблем и решение
Първоначални данни:
- файл, който съдържа потребителското име на всеки ред и е по-голям от количеството RAM, използвано от устройството;
- функция, която чете блок от данни от диска и го връща, подплатен със знак за нов ред.
Задача:
- Вземете потребителски имена, които започват с "A", "E" или "M".
- Направете заявка, като използвате получените данни към страницата http://jsonplaceholder.typicode.com/users?username= .
- Приложете определения набор от четири функции към първите четири отговора на сървъра.
Разделяме задачата на по-малки блокове, за да напишем отделна функция за всеки. В резултат на това получаваме следните функции:
- Първият връща всяко потребителско име ( getNextUsername ).
- Вторият избира имена, които започват с "A", "E" или "M" ( filterIfStartsWithAEOrM ).
- Третият прави мрежова заявка и връща Promise, обект за извеждане на резултата от изчислението (makeRequest).
Тези функции работят със стойности. За да ги приложим към списък, въвеждаме три функции от по-висок ред:
- Първият избира елементите от списъка въз основа на зададените параметри ( филтър ).
- Вторият прилага функцията към всеки елемент от списъка ( карта ).
- Третият прилага функции от един итератор към данните на друг итератор (zipWith с функция за опаковане).
„Мързелът“ на този подход може да бъде от полза, тъй като мрежовите заявки не се правят за всички имена, отговарящи на критериите за филтриране.
И така, имаме масив от функции, които трябва да бъдат изпълнени, за да обработим крайните отговори, и функция, която връща блокове от данни по "мързелив" начин. Нека напишем функция, използваща генератори, за да получим потребителски имена, използвайки мързелив подход.
Сега са необходими следните функции за работа със стойности:
Promise е "контейнер" за съхраняване на стойността на извършваната операция, която ще се появи в бъдеще. Интерфейсът Promise ви позволява да определите какви действия да предприемете, след като операцията е успешна или неуспешна. Ако операцията е успешна, манипулаторът за успех се извиква със стойността на операцията. В противен случай се извиква манипулаторът на грешки.
Сега нека напишем функциите на висшияпоръчка, която ще осигури работа с "мързеливи" списъци. Тяхната работа е да отложат изпълнението, докато не бъде направена заявка. В този случай са необходими стойности при поискване, така че генераторите ще дойдат на помощ.
Написани са необходимите функции. Ние използваме композиция, за да решим проблема.
lazyComposedResult - "мързелива" верига от изчисления (тръбопровод), съставена от функционални композиции. Нито една от връзките няма да се изпълни, докато горният композиционен блок, т.е. lazyComposedResult, не се изпълнява. Направихме само четири обаждания, въпреки че резултатът от филтъра може да съдържа повече от четири стойности.
В резултат на това получихме кратко решение на проблема, в което се появиха функции от най-високо ниво, композиции и функции за многократна употреба.