Как упростить реализацию функции pipe, комбинирующей несколько функций в JavaScript?
Что проверяют
Понимаете ли вы, что pipe это свёртка списка функций, и можете ли убрать из реализации лишнее, не потеряв поведения. Следом обычно просят написать compose, сделать асинхронную версию или объяснить, что вернёт pipe() без аргументов.
Как отвечать
Упрощённая версия укладывается в одну строку:
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x);
Дальше объясните, почему она корректна:
- rest-параметр
...fns: собирает функции в настоящий массив,argumentsи ручной цикл не нужны; - начальное значение
xвreduce: без него аккумулятором на первом шаге станет сама первая функция, а на пустом массивеreduceброситTypeError. С нимpipe()возвращает тождественную функцию; - порядок:
pipe(f, g)(x)этоg(f(x)), слева направо.composeделает то же черезreduceRight, справа налево; - несколько аргументов на входе: первую функцию отделяют через
const [first, ...rest] = fnsи возвращают(...args) => rest.reduce((acc, fn) => fn(acc), first(...args)); - асинхронность: если функции возвращают промисы, цепочку строят через
then:fns.reduce((p, fn) => p.then(fn), Promise.resolve(x)).
Хорошо показать, что вы знаете готовые решения: flow в lodash, pipe в fp-ts и в RxJS. В TypeScript честная типизация pipe делается перегрузками на фиксированное число функций, поэтому в библиотеках их по десятку и больше. Оператор |> пока остаётся предложением TC39 и в стандарт не вошёл.
Где спотыкаются
Пишут вариант fns.reduce((f, g) => (...args) => g(f(...args))) без начального значения. Он работает, пока передана хотя бы одна функция, а вызов pipe() без аргументов падает с TypeError.
Путают порядок pipe и compose и на вопрос «что выполнится первым» отвечают наугад.
Передают в синхронный pipe функцию, которая возвращает промис, и удивляются, что следующая получает объект Promise вместо значения. Нужна отдельная асинхронная версия, смешивать оба режима в одной функции не стоит.