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

Без композиции такой код быстро превращается в длинные вложенные вызовы. Он еще работает, но читать и менять его становится неприятно: приходится распутывать порядок вычислений прямо в момент чтения.

Ниже — когда композиция делает JavaScript-код проще, почему порядок аргументов влияет на переиспользование и как отлаживать pipeline без потери читаемости.

Обновлено 17 марта 2026: переписаны примеры, добавлен разбор pipe и практических правил для data-last функций.

Что такое композиция на простом примере

Начнем с трех маленьких функций:

const upperCase = (value) => value.toUpperCase();
const exclaim = (value) => `${value}!`;
const repeat = (value) => `${value} `.repeat(3).trim();

Если применить их напрямую, получится вложенный вызов:

repeat(exclaim(upperCase("I love coding")));
// I LOVE CODING! I LOVE CODING! I LOVE CODING!

Это и есть композиция, только записанная вручную. Проблема в том, что с ростом количества шагов вложенность начинает мешать.

compose и pipe

Чтобы упростить чтение, можно собрать функции в отдельную абстракцию.

const compose = (...fns) => (value) =>
  fns.reduceRight((acc, fn) => fn(acc), value);

const emphasize = compose(repeat, exclaim, upperCase);

emphasize("I love coding");

compose применяет функции справа налево. Если тебе удобнее читать pipeline слева направо, используй pipe.

const pipe = (...fns) => (value) =>
  fns.reduce((acc, fn) => fn(acc), value);

const emphasize = pipe(upperCase, exclaim, repeat);

Обе функции делают одно и то же. Разница только в порядке чтения. В JavaScript-командах pipe часто воспринимается проще, потому что ближе к тому, как мы обычно читаем шаги обработки данных.

Почему порядок аргументов так важен

Композиция особенно хорошо работает, когда функции ожидают данные последним аргументом. Тогда их легче частично применять и комбинировать.

Возьмем каррированную версию map:

const map = (fn) => (list) => list.map(fn);
const prop = (key) => (obj) => obj[key];

const getName = prop("name");
const getNames = map(getName);

getNames([
  { name: "Alex" },
  { name: "Julia" },
]); // ["Alex", "Julia"]

Почему это удобно:

  • getName можно переиспользовать в любом месте;
  • getNames уже готов к работе с любым массивом;
  • такие функции легко встраиваются в pipe или compose.

Если же данные идут первым аргументом, переиспользование обычно становится хуже. Ты привязываешь функцию к конкретному значению слишком рано.

Как отлаживать композицию

У композиции есть обратная сторона: когда pipeline длинный, не всегда очевидно, на каком шаге испортилось значение.

Самый простой способ - вставить tap.

const tap = (label) => (value) => {
  console.log(label, value);
  return value;
};

const pipe = (...fns) => (value) =>
  fns.reduce((acc, fn) => fn(acc), value);

const emphasize = pipe(
  tap("input"),
  upperCase,
  tap("after upperCase"),
  exclaim,
  repeat,
);

tap не меняет данные, а только показывает, что происходит на каждом этапе. Это особенно полезно в длинных pipeline и в асинхронных сценариях.

Когда композиция действительно окупается

Композиция хороша там, где:

  • есть несколько маленьких преобразований данных;
  • каждую функцию можно назвать отдельно;
  • шаги хочется переиспользовать в разных местах;
  • логика выглядит как явный pipeline.

Она хуже работает там, где:

  • каждая функция живет только ради одного места;
  • шаги слишком сильно завязаны на общий контекст;
  • без перехода по определениям невозможно понять даже простое выражение.

То есть композиция должна сокращать когнитивную нагрузку, а не переносить ее в десяток маленьких файлов.

Композиция функций полезна тогда, когда помогает мыслить шагами, а не вложенностями. Если код естественно складывается в цепочку преобразований, compose или pipe делают его чище, переиспользуемее и удобнее для тестирования.

Хороший ориентир: начни с маленьких функций, которые делают одну вещь, и держи данные последним аргументом. Именно после этого композиция начинает приносить реальную пользу, а не просто красиво выглядеть.