Pipeline operator in JS.
Самая ожидаемая фича от JavaScript уже в течении многих лет, но продвижение которой по
стейджам tc39 предложений уже не происходит очень давно.
Почему она так ожидаема и почему она так долго идет? Для начала разберемся с проблематикой
Вот у нас есть получение некоторых данных на основе, очень частый кейс который приходится писать в работе с данными:
const = Object.fromEntries(Object.entries(someData).filter(([,value]) => !!value).map(([key, value]) => [key, value * 2]))
Строка длинная и естественно ее можно разбить на строки, пытаться отформатировать или разбить на переменные (имена которые порой ой как больно придумывать, потому что нам они НЕ ИНТЕРЕСНЫ, нам нужен лишь результат, а не 10 промежуточных значений). Форматирование строки тоже не даст много полезных результатов, так как тут есть некоторая вложенность. Что же предлагает нам пропозал?
const = Object.entries(someData) |> %.filter(([,value]) => !!value) |> %.map(([key, value]) => [key, value * 2])) |> Object.fromEntries(%)
Строка стала чуть длиннее, зато нам теперь совершенно очевиден порядок для разбиения
const = Object.entries(someData)
|> %.filter(([,value]) => !!value)
|> %.map(([key, value]) => [key, value * 2]))
|> Object.fromEntries(%)
Теперь суть кода считается последовательно и без запоминания каких-то контекстов и это совсем базовый сценарий. Магия происходит когда мы задумываемся как мы раньше пытались сделать вот такой "поток" мысли: правильно, чейнингом. Функция возвращала объект в котором лежат методы которые вновь возвращают объект и так далее. Но мы с вами упирались в один момент: методы уже должны быть заложены в объекте. Те возьмем обработку массива
Object.entries(someData)
.filter(([,value]) => !!value)
.map(([key, value]) => [key, value * 2]))
// и вот следующего оператора нет в Array, зато он есть в Object
Object.groupBy(/*тут должен был быть наш массив*/)
Те как только нужно было использовать метод которого нет в объекте, то вся магия тут же ломалась и мы вынуждены были либо делать уродливое вложение, либо создавать сущность которая нам не интересна (единственная ее задача переложить прошлое значение как аргумент)
И вот тут приходит на помощь pipe
Object.entries(someData)
|> %.filter(([,value]) => !!value)
|> %.map(([key, value]) => [key, value * 2]))
|> Object.groupBy(%, grouper)
|> logAndReturn(%)
|> sendToClient(%)
Как мы видим в пайплайне ему не интересно чейнинг у нас или наши кастомные методы, он позволяет расширять возможности при использовании не пытаясь расширить прототипы для удобства. Особенно сильно это заметно на таких возможностях, которые подсаживают на чейнинг, как например
Iterator HelpersIterator.from([1,2,3,4,5,6])
.filter((num) => num % 2)
.map((num) => num * 2 + 1)
.drop(1) // откидываем первое число
zip() // а вот zip в нем нет, думайте сами как сюда такую функцию вкорячить
Суть думаю ясна, что такие API очень завязаны только на встроенные возможности и юзать что-то свое с ними тем самым расширяя API не слишком и приятно. И вот такие API мог бы полностью раскрыть Pipeline Operator так как он может в 1 цепочку объединять разнородное API.