Operator pipeline |> w JavaScript to propozycja nowej składni, która pozwala zapisywać złożone łańcuchy przekształceń danych w prosty, czytelny, od lewej do prawej sposób – bez zagnieżdżania funkcji i „spaghetti” z nawiasów.
Mimo że nie jest jeszcze częścią standardu i wymaga transpilerów (np. Babel), już dziś warto go poznać, bo realnie poprawia czytelność i dostępność kodu – także dla mniej doświadczonych członków zespołu.
Dlaczego w ogóle potrzebujemy operatora pipeline?
W typowym kodzie front‑endowym wykonujemy na danych wiele operacji, na przykład:
- normalizacja i walidacja danych z formularza,
- filtrowanie i transformacje kolekcji (listy produktów, rekordy z API),
- przygotowanie danych do wyświetlenia (formatowanie dat, cen, etykiet).
Już w „czystym” JavaScripcie można to zapisać co najmniej na trzy sposoby:
1. Zagnieżdżone wywołania funkcji
Przykład:
const result = formatPrice( applyDiscount( parseFloat( normalizeInput(rawValue) ) ) );
Czytamy to „od środka na zewnątrz”, co jest sprzeczne z tym, jak naturalnie przetwarzamy informacje (od lewej do prawej). Dużo nawiasów, trudniej śledzić przepływ danych.
2. Metody łańcuchowe
Gdy operujemy na tablicach, mamy wygodne podejście. Przykład:
const result = items
.filter(isAvailable)
.map(applyDiscount)
.map(formatPrice);
To jest już dużo lepsze, ale działa głównie na typach, które mają metody (Array, String, niektóre biblioteki). Jeśli mamy zwykłe funkcje, wracamy do zagnieżdżania lub do tworzenia wielu zmiennych pośrednich.
3. Zmienne pośrednie („krok po kroku”)
Przykład:
const normalized = normalizeInput(rawValue);
const parsed = parseFloat(normalized);
const discounted = applyDiscount(parsed);
const result = formatPrice(discounted);
Czytelne, ale rozdmuchane – dużo nazw zmiennych, które żyją tylko po to, by przekazać wartość dalej.
A teraz sedno: operator pipeline ma połączyć zalety tych podejść –
- czytelność i „płynność” metod łańcuchowych,
- ogólność zwykłych funkcji,
- minimalną liczbę nawiasów i zmiennych pośrednich.
Czym jest operator pipeline |> w JavaScript?
Operator pipeline (|>) to proponowana konstrukcja języka JavaScript, która pozwala przesłać wynik jednej instrukcji jako wejście do kolejnej, zapisując to w formie liniowego łańcucha.
Najprostsza mentalna definicja:
a |> foznacza „weź wartośćai przekaż ją do funkcji/wyrażeniaf”.
Zapis równoważny dla prostych funkcji (wariant „minimal / F# style”) wygląda tak:
value |> fn
// jest równoważne
fn(value)
Możemy też łączyć wiele kroków. Przykład:
value |> fnA |> fnB |> fnC;
W takim zapisie zachodzi następujący przepływ:
valuetrafia dofnA,- wynik
fnAtrafia dofnB, - wynik
fnBtrafia dofnC.
To dokładnie tak, jak w znanym z powłoki Unix operatorze | – wyjście jednego kroku jest wejściem następnego.
Status propozycji: co dziś naprawdę działa?
Operator pipeline NIE jest jeszcze częścią standardu ECMAScript.
Różne źródła podają nieco inne statusy w procesie TC39:
- część artykułów opisuje go jako Stage 2 – czyli zaawansowaną propozycję, nad którą trwają prace nad szczegółami,
- inne, bardziej aktualne, mówią, że w 2024 r. propozycja była na etapie Stage 1 – co oznacza, że wciąż jest eksplorowana i może się istotnie zmienić.
Z repozytorium TC39 wiemy, że cel propozycji jest stały: połączenie wygody łańcuchowania metod z elastycznością zagnieżdżania wyrażeń przy użyciu wzorca value |> e1 |> e2 |> e3.
W praktyce:
- operator nie jest wspierany natywnie przez przeglądarki ani Node.js,
- aby używać go dziś w projekcie, potrzebujesz transpilera, najczęściej Babel z odpowiednią wtyczką,
- składnia wciąż może się zmienić (np. wybór wariantu „minimal” vs „Hack pipes”).
Dlatego warto traktować go obecnie jako:
- świetne narzędzie do nauki funkcjonalnego stylu i pisania czytelnego kodu,
- opcję do projektów zbudowanych wokół nowoczesnego toolchainu (Babel, bundlery),
- ale z zachowaniem świadomości, że to wciąż eksperyment.
Dwa główne style składni – „minimal” vs „hack pipes”
1. Wariant „minimal / F# style”
W tym podejściu zapis:
value |> fn
jest równoważny wywołaniu:
fn(value);
A dłuższy łańcuch:
value |> normalizeInput
|> parseFloat
|> applyDiscount
|> formatPrice;
odpowiada:
formatPrice(
applyDiscount(
parseFloat(
normalizeInput(value)
)
)
);
Ten wariant jest intuicyjny, gdy każda funkcja przyjmuje dokładnie jeden argument (ten „przepuszczany” przez łańcuch) i nie trzeba dodawać dodatkowych parametrów. Wtyczka Babel może być skonfigurowana właśnie w trybie „minimal” ("proposal": "minimal").
2. Wariant „hack pipes” z tokenem %
Nowsze dyskusje TC39 skłaniają się ku tzw. Hack pipes, gdzie w każdym etapie pipeline’u używa się specjalnego „tokena tematycznego” (topic token), np. %, który oznacza „wynik z poprzedniego kroku”.
Przykład:
const result = value
|> normalizeInput(%)
|> parseFloat(%)
|> applyDiscount(% , 0.2)
|> formatPrice(%);
Zgodnie z opisem tej wersji:
- operator pipeline bierze wartość z lewej strony,
- w wyrażeniu po prawej stronie token
%reprezentuje tę wartość, - każdy krok jest zwykłym wyrażeniem, które może używać
%w dowolnym miejscu.
Zalety tego podejścia można streścić następująco: łatwo dodajesz dodatkowe argumenty (np. applyDiscount(% , 0.2)) bez funkcji pomocniczych i możesz używać pipeline’u z niemal dowolnymi wyrażeniami, a nie tylko z prostymi nazwami funkcji. Składnia może się jednak jeszcze zmienić – obecny kształt Hack pipes to nadal propozycja.
Krótkie porównanie – ten sam kod trzema stylami
Zacznijmy od definicji funkcji:
const normalizeInput = str => str.trim();
const parsePrice = str => parseFloat(str.replace(',', '.'));
const applyDiscount = (value, ratio) => value * (1 - ratio);
const formatPrice = value => value.toFixed(2) + ' zł';
Bez pipeline – zagnieżdżanie
Przykład:
const result = formatPrice(
applyDiscount(
parsePrice(
normalizeInput(rawValue)
),
0.2
)
);
Bez pipeline – krok po kroku
Przykład:
const normalized = normalizeInput(rawValue);
const parsed = parsePrice(normalized);
const discounted = applyDiscount(parsed, 0.2);
const result = formatPrice(discounted);
Pipeline – wariant „minimal” (jednoargumentowe funkcje)
Tu applyDiscount przyjmuje dwa argumenty, więc „opakowujemy” ją w funkcję częściowo zastosowaną:
const apply20Discount = value => applyDiscount(value, 0.2);
const result = rawValue
|> normalizeInput
|> parsePrice
|> apply20Discount
|> formatPrice;
Pipeline – „hack pipes” z %
Przykład:
const result = rawValue
|> normalizeInput(%)
|> parsePrice(%)
|> applyDiscount(% , 0.2)
|> formatPrice(%);
W obu wersjach przepływ danych jest czytelny, liniowy i zgodny z kolejnością kroków, jaką mamy w głowie.
Jak pipeline upraszcza łańcuchy funkcji w praktyce
1. Transformacje danych (np. z formularza)
Wyobraźmy sobie typową ścieżkę danych z formularza:
- obcięcie białych znaków,
- zamiana pustych stringów na
null, - walidacja,
- przygotowanie obiektu do wysłania na API.
Bez pipeline możemy mieć coś takiego:
const trimmed = input.trim();
const normalized = trimmed === '' ? null : trimmed;
const validated = validate(normalized);
const payload = toApiPayload(validated);
const finalPayload = freeze(payload);
Z pipeline – w czytelnej wersji z małymi funkcjami pomocniczymi:
const nullIfEmpty = str => str === '' ? null : str;
const finalPayload = input
|> normalizeInput(%)
|> nullIfEmpty(%)
|> validate(%)
|> toApiPayload(%)
|> freeze(%);
To podejście:
- łatwo rozbudować o kolejne kroki,
- naturalnie się czyta (z góry na dół, z lewej do prawej),
- utrzymuje skupienie na „przepływie” zamiast na nawiasach.
2. Przetwarzanie odpowiedzi z API
Przykład: pobieramy listę produktów, filtrujemy i przygotowujemy do prezentacji.
Bez pipeline:
fetch('/api/products')
.then(res => res.json())
.then(data => data.filter(isAvailable))
.then(products => products.map(prepareForUI))
.then(renderProducts)
.catch(showError);
Z pipeline (ideowo, z osobnym etapem przekształceń):
const processProducts = data => data
|> filterAvailable(%)
|> enrichWithLabels(%)
|> prepareForUI(%);
fetch('/api/products')
.then(res => res.json())
.then(data => data |> processProducts(%))
.then(renderProducts)
.catch(showError);
Pipeline buduje czysty, testowalny fragment transformacji, który można łatwo ponownie wykorzystać.
3. Łączenie z istniejącym łańcuchowaniem metod
Pipeline dobrze uzupełnia API, które już stosuje łańcuchowanie:
const result = users
.filter(isActive)
.map(toPublicProfile)
.map(addAvatarUrl);
Zamiast zagnieżdżać „zwykłe” funkcje, można użyć pipeline:
const viewModel = users
.filter(isActive)
.map(toPublicProfile)
.map(addAvatarUrl)
|> groupByRole(%)
|> sortByName(%);
Zyskujemy spójny, jednolity styl zapisu, niezależnie od tego, czy korzystamy z metod obiektów, czy funkcji „wolnostojących”.
Jak operator pipeline wpływa na „dostępność” kodu?
Serwisy o dostępności zwykle koncentrują się na warstwie UI (WCAG, ARIA itd.), ale dostępność kodu dla zespołu ma realne przełożenie na:
- tempo wdrażania zmian,
- liczbę błędów,
- możliwość udziału juniorów i osób spoza wąskiej specjalizacji,
- łatwość code review.
Operator pipeline sprzyja tej „wewnętrznej” dostępności na kilka sposobów:
-
Czytelność zgodna z naturalnym sposobem czytania
Pipeline wymusza zapis kroków w kolejności ich wykonywania, od lewej do prawej i z góry na dół, co jest zgodne z tym, jak większość osób czyta i analizuje instrukcje. -
Mniejszy „szum syntaktyczny”
Mniej nawiasów, mniej zagnieżdżeń, mniej tymczasowych nazw zmiennych – dzięki temu kod jest bardziej „płaski” wizualnie, co obniża obciążenie poznawcze przy analizie. -
Łatwiejsze tłumaczenie kodu na „język naturalny”
Fragment:
const result = value
|> stepA(%)
|> stepB(%)
|> stepC(%);
można czytać: „weź value, zastosuj krok A, potem B, potem C”. Taki styl ułatwia współpracę z osobami mniej technicznymi (np. analitycy, UX), które uczestniczą w przeglądach.
-
Lepsza separacja kroków i testowalność
Każdy etap pipeline’u jest osobną funkcją/wyrażeniem, które można testować niezależnie. To sprzyja tworzeniu małych, łatwych do zrozumienia modułów – analogicznie do dobrych praktyk dostępnościowych w UI (jasne, modularne komponenty). -
Spójny styl
Zamiast mieszać różne idiomy (raz zagnieżdżanie, raz łańcuchy, raz seria zmiennych), można wprowadzić jednolity wzorzec pipeline dla przekształceń danych. Zespół ma mniej wyjątków do zapamiętania, co poprawia ogólną ergonomię pracy z kodem.
Jak używać operatora pipeline dziś (Babel)
Ponieważ pipeline nie jest jeszcze wspierany natywnie, potrzebujemy etapu kompilacji, który przekształci nową składnię do zwykłego JavaScriptu zrozumiałego dla przeglądarki i Node.
Krok 1 – projekt z Node i Babel
Minimalny scenariusz wygląda następująco:
- zainstaluj Node.js,
- utwórz katalog projektu i zainicjalizuj
package.json, - zainstaluj Babel oraz wtyczkę pipeline.
Przydatne pakiety:
@babel/core,@babel/cli,@babel/plugin-proposal-pipeline-operator.
Krok 2 – konfiguracja Babela
W pliku konfiguracyjnym Babela (np. .babelrc) ustaw wariant składni:
{
"plugins": [
["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]
]
}
Parametr "proposal": "minimal" wybiera wariant „minimal / F# style”. Dostępne są również inne warianty (w tym Hack pipes) – konfiguracja zależy od wersji wtyczki i przyjętego w projekcie stylu.
Krok 3 – kompilacja i uruchamianie
W skryptach package.json dodaj komendy, które kompilują plik źródłowy (np. main.js) do pliku wynikowego (np. output.js) i go uruchamiają. Wtedy możesz pisać kod z pipeline, np.:
const result = rawValue
|> normalizeInput
|> parsePrice
|> apply20Discount
|> formatPrice;
Babel przekształci go do równoważnego kodu bez operatora |>, który zadziała we wspieranych środowiskach.
Dobre praktyki korzystania z operatora pipeline
1. Używaj pipeline’u do czystych transformacji
Pipeline błyszczy, gdy każdy etap przyjmuje wartość i zwraca nową wartość (bez skutków ubocznych) oraz gdy budujesz czyste, przewidywalne funkcje, łatwe do testowania. Przykład:
const toSlug = str => str
|> normalize(%)
|> removeDiacritics(%)
|> toLowerCase(%)
|> replaceSpacesWithDash(%);
2. Unikaj ciężkich skutków ubocznych w środku pipeline’u
Działania typu logToAnalytics(%), modyfikacja DOM czy zapis do Local Storage w środku pipeline’u mogą utrudniać zrozumienie przepływu (bo pipeline „kojarzy się” z transformacjami). Lepiej wydzielić je na początek/koniec lub jasno nazwać funkcję z efektem ubocznym (np. logAndReturnSameValue).
3. Dbaj o czytelne nazwy kroków
Dzięki pipeline’owi kod może przypominać dokumentację. Przykład:
const sanitizedHtml = html
|> stripDangerousTags(%)
|> sanitizeAttributes(%)
|> removeTrackingScripts(%)
|> addRelNoopenerToLinks(%);
Taki fragment sam wyjaśnia, co się dzieje – ważne i dla czytelności, i dla „dostępności” kodu w zespole.
4. Nie przesadzaj z długością łańcuchów
Zbyt długie pipeline’y (np. 15–20 kroków) będą tak samo trudne do analizy jak gigantyczne łańcuchy metod. W takich sytuacjach lepiej grupować kroki w mniejsze funkcje pomocnicze i nadawać im znaczące nazwy.
const normalizeUserInput = input => input
|> trimAndNormalize(%)
|> applyDefaultValues(%)
|> validateShape(%);
const userPayload = rawInput
|> normalizeUserInput(%)
|> mapToApiPayload(%);
5. Pamiętaj o statusie propozycji
Ponieważ pipeline jest eksperymentalny:
- unikaj uzależniania krytycznego kodu produkcyjnego od jego konkretnego wariantu składniowego,
- miej świadomość, że aktualizacja Babela/wtyczki może wymagać zmian w kodzie, jeśli propozycja ewoluuje,
- rozważ stosowanie go najpierw w nowych, odizolowanych modułach lub w częściach aplikacji, które łatwo zrefaktoryzować.
Przykłady z życia twórcy stron WWW
1. Przetwarzanie danych formularza kontaktowego
Załóżmy funkcje pomocnicze:
const trimAllFields = data => ({ ...data, message: data.message.trim() });
const stripHtml = data => ({ ...data, message: stripTags(data.message) });
const limitLength = data => ({ ...data, message: data.message.slice(0, 2000) });
const validateContact = data => {
// rzuca błąd lub zwraca dane
return data;
};
const toApiPayload = data => ({ ...data, source: 'contact-form' });
Pipeline składania payloadu wygląda następująco:
const buildContactPayload = rawData => rawData
|> trimAllFields(%)
|> stripHtml(%)
|> limitLength(%)
|> validateContact(%)
|> toApiPayload(%);
- łatwo dodać kolejne kroki (np. wykrywanie linków spamowych),
- każdy krok jest testowalny,
- czytelność jest bardzo wysoka.
2. Przygotowanie danych do komponentu (np. React/Vue)
Załóżmy, że otrzymujemy surowe dane użytkownika z API. Możemy je wzbogacić tak:
const enrichUserForProfile = user => user
|> mapBackendFieldsToFrontend(%)
|> attachAvatarUrl(%)
|> attachRoleLabel(%)
|> attachAccessibilityFlags(%); // np. czy wymaga większego kontrastu itp.
Taki pipeline jest wielokrotnego użytku (np. widok profilu, lista użytkowników), a krok attachAccessibilityFlags może agregować informacje potrzebne do dostępnego UI (kontrast, rozmiar czcionki, preferencje użytkownika).






