Siti menerima judul lokakarya dari form pendaftaran, dan isinya hampir selalu berantakan: spasi berlebih di awal dan akhir, spasi ganda di tengah, huruf kecil semuanya. Sebelum ditampilkan, teks itu harus melalui tiga langkah tetap — dibersihkan, dipotong agar muat di kartu, lalu huruf pertamanya dikapitalkan. Tiga langkah kecil, tetapi menyusunnya sebagai satu alur ternyata punya seni tersendiri.

Penggabungan function menjadi alur seperti ini disebut function composition: keluaran satu function menjadi masukan function berikutnya, sehingga tugas besar terurai menjadi deretan function kecil yang masing-masing mengerjakan satu hal. Susunan ini berpadanan dengan rantai map dan filter pada data array; bedanya, composition berlaku pada nilai apa pun, bukan hanya daftar. Chapter ini memulai dari bentuk paling kasarnya — panggilan bersarang — lalu membangun pipe, sebuah builder kecil yang menyusun function secara berurutan, dan mengujinya dengan pipeline pemrosesan teks yang benar-benar berjalan.

Function Kecil dengan Tugas Tunggal #

Berkas berikut membangun semuanya. Tiga function pertama masing-masing mengerjakan satu tugas, semuanya murni sesuai kontrak chapter Pure Functions — teks masuk, teks keluar, tanpa menyentuh apa pun di luar parameternya. Ukuran kecil bukan kebetulan: function satu tugas mudah diuji, mudah dipakai ulang di pipeline lain, dan kesalahannya mudah dilacak ke satu tempat. bersihkan misalnya, tidak tahu dan tidak peduli bahwa keluarannya kelak dipotong; ia hanya menjamin satu hal — teks yang diterimanya kembali tanpa spasi berlebih.

// pipeline-teks.js
// Function composition: function kecil disusun menjadi pipeline.

// --- 1. tiga function kecil, satu tugas masing-masing ---
function bersihkan(teks) {
  return teks.trim().replace(/\s+/g, " ");
}

function potong(teks, batas) {
  return teks.slice(0, batas);
}

function kapital(teks) {
  return teks.charAt(0).toUpperCase() + teks.slice(1);
}

// --- 2. tanpa pipeline: panggilan bersarang ---
const mentah = "   lokakarya  javascript   pemula   ";
console.log(bersihkan(mentah));
console.log(kapital(potong(bersihkan(mentah), 20)));

// --- 3. pipe: function builder dari reduce ---
function pipe(...fns) {
  return function (nilaiAwal) {
    return fns.reduce((nilai, fn) => fn(nilai), nilaiAwal);
  };
}

const siapkanJudul = pipe(bersihkan, (teks) => potong(teks, 20), kapital);

console.log(siapkanJudul(mentah));
console.log(siapkanJudul("  dasar  web  untuk   semua orang  "));
console.log(siapkanJudul("budi"));

// --- 4. compose: arah penggabungan dibalik ---
function compose(...fns) {
  return function (nilaiAwal) {
    return fns.reduceRight((nilai, fn) => fn(nilai), nilaiAwal);
  };
}

const siapkanJudulBalik = compose(
  kapital,
  (teks) => potong(teks, 20),
  bersihkan
);

console.log(siapkanJudulBalik(mentah));

Output yang Dihasilkan:

text
lokakarya javascript pemula
Lokakarya javascript
Lokakarya javascript
Dasar web untuk semu
Budi
Lokakarya javascript

Mari kita telaah baris-baris pentingnya:

  1. Baris 5–7 (bersihkan): trim() menghapus spasi di kedua ujung, lalu replace(/\s+/g, " ") mengganti setiap rangkaian spasi di tengah menjadi satu spasi — pola regex dari section Strings dan Regex.
  2. Baris 9–15 (potong dan kapital): potong memangkas teks sampai batas karakter, dan kapital membesarkan huruf pertama saja. Ketiga function kecil ini adalah bahan baku seluruh chapter.
  3. Baris 20 (panggilan bersarang): tiga langkah disambung secara manual, dan pembacanya harus menyusuri dari dalam ke luar — bersihkan dulu, lalu potong dengan batas 20, terakhir kapital. Hasilnya benar (Lokakarya javascript), tetapi urutan eksekusi tertulis terbalik dengan urutan membaca.
  4. Baris 23–27 (pipe): inilah builder dari section sebelumnya dalam bentuk paling ringkas. Rest parameter ...fns menampung seluruh function yang dikirim, dan function yang dikembalikan menjalankan reduce — nilai akumulator dimulai dari nilaiAwal, lalu setiap function menelan hasil function sebelumnya: fn(nilai).
  5. Baris 29 (siapkanJudul): pipeline terbentuk. Perhatikan (teks) => potong(teks, 20) — function penghubung kecil yang mengunci batas 20, karena potong sendiri membutuhkan dua argumen sedangkan pipeline mengalirkan satu nilai.
  6. Baris 31–33 (tiga uji): siapkanJudul dipanggil tiga kali dengan teks berbeda. Urutan penulisannya kini sama dengan urutan eksekusinya: bersihkan, potong, kapital. Teks pendek seperti "budi" lolos tanpa dipotong dan kembali sebagai "Budi".
  7. Baris 36–48 (compose): versi arah sebaliknya — reduceRight menjalankan daftar function dari kanan ke kiri. Argumentasi compose ditulis dalam urutan eksekusi dari terakhir ke pertama, sehingga siapkanJudulBalik menghasilkan keluaran yang sama persis dengan siapkanJudul.

Mekanisme pipe dalam Satu Reduce #

Karena pipe adalah inti chapter ini, mari kita jalankan satu langkah sekaligus secara manual:

js
const langkahPertama = bersihkan("   judul   panjang sekali   ");
const langkahKedua = kapital(langkahPertama);
console.log(langkahPertama);
console.log(langkahKedua);

Output yang Dihasilkan:

text
judul panjang sekali
Judul panjang sekali

Bentuk fns.reduce((nilai, fn) => fn(nilai), nilaiAwal) pada pipe melakukan persis rangkaian ini: variabel nilai diawali nilaiAwal, lalu pada setiap putaran reduce mengganti nilai dengan hasil function berikutnya. Setelah function terakhir selesai, hasil akhir menjadi nilai kembalian reduce — dan sekaligus kembalian pipeline. Panjang daftarnya bebas: menambah langkah berarti menambah satu elemen, bukan menambah satu lapis kurung.

Perhatikan pula bahwa pipe sendiri adalah function murni: ia tidak menyentuh function-function yang diterimanya, hanya menyusun urutan panggilannya. Karena itu pipeline yang sudah jadi pun bisa dijadikan bahan pipeline yang lebih besar — function hasil pipe hanyalah function biasa bagi pemanggilnya.

Pipeline untuk Satu Daftar Judul #

siapkanJudul menghasilkan function yang menerima satu teks dan mengembalikan satu teks — bentuk yang persis cocok untuk callback map. Bila form Siti mengirim banyak judul sekaligus, seluruh daftar bisa disiapkan dengan satu pemanggilan map:

js
const judulMentah = ["  promo   kopi   senin  ", "  latte  baru  "];
console.log(judulMentah.map(siapkanJudul));

Output yang Dihasilkan:

text
[ 'Promo kopi senin', 'Latte baru' ]

Tidak ada loop tambahan yang perlu ditulis: map mengalirkan setiap elemen ke dalam pipeline, dan pipeline mengembalikan judul yang sudah rapi. Inilah titik temu dua bentuk composition — rantai method mengolah daftar, pipeline mengolah satu nilai, dan keduanya bisa disusun bertingkat sesuai kebutuhan.

Syarat Rangkaian yang Sehat #

Composition tidak menuntut function apa pun, tetapi rangkaian yang nyaman adalah yang setiap mata rantainya mengambil satu masukan dan menghasilkan satu keluaran. Function dengan dua parameter seperti potong tetap boleh dipakai — cukup dibungkus function penghubung yang mengunci argumen kedua, seperti (teks) => potong(teks, 20) pada berkas contoh. Cara lain mengunci argumen awal, lewat bind, dibahas di chapter Functional Patterns.

Nama mata rantainya juga bekerja sebagai dokumentasi. Deretan bersihkan, potong, kapital bisa dibaca keras-keras sebagai kalimat lengkap, dan pembaca baru tidak perlu membuka isi tiap function untuk memahami alurnya. Bila satu langkah belum punya nama yang menggambarkan tujuannya, berilah nama function terpisah — biaya menulis tiga baris kecil jauh lebih murah daripada biaya membaca alur tanpa penanda.

Composition mengalirkan data dari function ke function tanpa state antara, dan sebagian besar rangkaiannya dibangun dari function murni. Ada satu keterampilan lain yang melengkapi kotak peralatan functional: mengelola state yang memang harus tetap hidup antar-pemanggilan — counter, cache, dan data privat. Itulah bahasan chapter Closures in Practice.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

pipeline-teks.js

Unduh berkas
javascript
// pipeline-teks.js
// Function composition: function kecil disusun menjadi pipeline.

// --- 1. tiga function kecil, satu tugas masing-masing ---
function bersihkan(teks) {
  return teks.trim().replace(/\s+/g, " ");
}

function potong(teks, batas) {
  return teks.slice(0, batas);
}

function kapital(teks) {
  return teks.charAt(0).toUpperCase() + teks.slice(1);
}

// --- 2. tanpa pipeline: panggilan bersarang ---
const mentah = "   lokakarya  javascript   pemula   ";
console.log(bersihkan(mentah));
console.log(kapital(potong(bersihkan(mentah), 20)));

// --- 3. pipe: function builder dari reduce ---
function pipe(...fns) {
  return function (nilaiAwal) {
    return fns.reduce((nilai, fn) => fn(nilai), nilaiAwal);
  };
}

const siapkanJudul = pipe(bersihkan, (teks) => potong(teks, 20), kapital);

console.log(siapkanJudul(mentah));
console.log(siapkanJudul("  dasar  web  untuk   semua orang  "));
console.log(siapkanJudul("budi"));

// --- 4. compose: arah penggabungan dibalik ---
function compose(...fns) {
  return function (nilaiAwal) {
    return fns.reduceRight((nilai, fn) => fn(nilai), nilaiAwal);
  };
}

const siapkanJudulBalik = compose(
  kapital,
  (teks) => potong(teks, 20),
  bersihkan
);

console.log(siapkanJudulBalik(mentah));