Краткое введение в pipe () и compose () в JavaScript
Pipe
Концепция pipe проста - сочетание n функций в себе. Это конвейер, идущий слева на право, вызывающий каждую функцию с выводом последней.
Напишем функцию, которая возвращает чей-то name.
getName = (person) => person.name;
getName({ name: 'Buckethead' });
// 'Buckethead'Напишем функцию, которая переводит строки в верхний регистр.
uppercase = (string) => string.toUpperCase();
uppercase('Buckethead');
// 'BUCKETHEAD'
Итак, если бы мы хотели получить имя person в верхнем регистре , мы могли бы написать так:
name = getName({ name: 'Buckethead' });
uppercase(name);
// 'BUCKETHEAD'Мы можем сократить это действие, исключив промежуточную переменную name.
uppercase(getName({ name: 'Buckethead' }));Уже лучше, но вложения могут стать слишком глубокими и с этим будет не удобно работать. Что, если мы хотим добавить функцию, которая получает первые 6 символов строки?
get6Characters = (string) => string.substring(0, 6);
get6Characters('Buckethead');
// 'Bucket'В результате чего:
get6Characters(uppercase(getName({ name: 'Buckethead' })));
// 'BUCKET';Давайте сойдемся с ума и добавим ещё функцию для переворота строк.
reverse = (string) =>
string
.split('')
.reverse()
.join('');
reverse('Buckethead');
// 'daehtekcuB'Теперь у нас есть:
reverse(get6Characters(uppercase(getName({ name: 'Buckethead' }))));
// 'TEKCUB'Это выглядит немного громоздким...
Pipe на помощь!
Вместо того, чтобы вклинивать функции внутри функций или создавать кучу промежуточных переменных, давайте используем pipe и всё!
pipe(
getName,
uppercase,
get6Characters,
reverse
)({ name: 'Buckethead' });
// 'TEKCUB'Чистое искусство. Это как список дел!
Давайте разберём.
Для демонстрационных целей, будем использовать pipeреализацию из одной статьи Эрика Эллиотта.
pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
Мы можем передавать n функции по конвейеру. Каждая функция берет результат предыдущей, и всё это сводится к единственному значению.
Передав, написанные ранее, функции, мы можем использовать pipe, как это сделали выше:
pipe(
getName,
uppercase,
get6Characters,
reverse
)({ name: 'Buckethead' });
// 'TEKCUB'Для пошаговой демонстрации добавим несколько отладчиков и пройдём строка за строкой.
pipe = (...functions) => (value) => {
debugger;
return functions.reduce((currentValue, currentFunction) => {
debugger;
return currentFunction(currentValue);
}, value);
};Вызовите pipe с нашими наработками и начнутся чудеса.
Проверим локальные переменные. functionsпредставляет собой массив из 4 функций, а value и есть { name: 'Buckethead' }.
Помимо наших параметров в pipe можно использовать любое количество функций. Он просто зациклится и будет вызывать каждую из них.
В следующем отладчике мы внутри reduce. Здесь currentValueпередается currentFunctionи возвращается.
Мы видим результат 'Buckethead'потому, что currentFunctionвозвращает .nameсвойство любого объекта. На данном этапе будет возвращен reduce, а на следующем currentValue уже станет новым . Давайте перейдем к следующему отладчику и посмотрим.
Теперь currentValueэто , ‘Buckethead’потому что это то , что нам вернула предыдущая функция. Сейчас currentFunction это uppercase, поэтому'BUCKETHEAD'будет в следующем currentValue.
По тому же сценарию из ‘BUCKETHEAD’ возьмутся первые 6 символов и передадутся следующей функции.
reverse(‘.aedi emaS’)
Ура мы это сделали!
А что на счёт compose()?
Это тот же pipe, просто, в другом направлении.
Поэтому, если вы хотите получить тот же результат, что и с помощью нашего pipeвыше, необходимо произвести все те же действия, но наоборот.
compose(
reverse,
get6Characters,
uppercase,
getName
)({ name: 'Buckethead' });Обратите внимание, чтоgetName уже последний в цепочке, а reverseпервый.
Вот быстрая реализация compose, снова любезно предоставленная Эриком Элиоттом из той же статьи.
Перевод статьи Язида Бзадоу.
Если вы хотите ещё больше познавательного контента из мира Java Script, присоединитесь к Telegram каналу @js_by_vladilen.