April 12, 2021

Краткое введение в pipe () и compose () в JavaScript

Abstract вектор создан(а) macrovector - ru.freepik.com

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.