Soal 1Saat kamu menulis orders.forEach(printOrder), siapa yang memanggil printOrder?
Higher-Order Function dan Callback — Meneruskan dan Mengembalikan Function
Pelajari higher-order function, yang menerima function sebagai argument, dan callback. Termasuk forEach, filter buatan sendiri, dan function yang mengembalikan function.
Kadang kamu ingin melakukan hal yang sedikit berbeda pada list yang sama: sekadar menampilkan tiap item, menyaringnya dengan suatu kondisi, atau menjumlahkan nominalnya. Kerangka loop-nya sama; yang berbeda hanya apa yang dilakukan pada tiap item.
Artikel ini membahas higher-order function, yang menerima function sebagai argument, dan callback, yaitu function yang diteruskan lalu dipanggil.
Meneruskan Logika sebagai Argument — Callback dan forEach
Misalnya kamu ingin menampilkan daftar pesanan satu per satu. Dengan for...of memang bisa, tapi kamu jadi menulis loop yang sama setiap kali menampilkan sebuah list, padahal yang berubah hanya cara tiap item ditampilkan.
Higher-order function (function yang menerima function sebagai argument atau mengembalikan function) memungkinkan kamu memisahkan kerangka loop dari apa yang dilakukan pada tiap item. Function yang diteruskan sebagai argument lalu dipanggil oleh function penerimanya disebut callback. Method array forEach memanggil callback yang kamu teruskan satu kali untuk setiap element.
const orders = [
{ id: "A-1101", item: "Buku tulis", amount: 480 },
{ id: "A-1102", item: "Pulpen", amount: 150 },
{ id: "A-1103", item: "Sticky note", amount: 320 },
];
// Function ini hanya mengurus tampilan satu item
const printOrder = (order) => {
console.log(`${order.id} ${order.item} ${order.amount} yen`);
};
// Cukup teruskan nama function-nya. Yang memanggil adalah forEach
orders.forEach(printOrder);
// A-1101 Buku tulis 480 yen
// A-1102 Pulpen 150 yen
// A-1103 Sticky note 320 yen
printOrder, dan forEach yang memanggilnya. Yang berubah di setiap pemanggilan hanyalah element yang masuk ke order.Baris orders.forEach(printOrder) tidak berisi kondisi loop maupun index. Untuk mengubah cara item ditampilkan, kamu cukup mengedit printOrder, dan function yang sama bisa diteruskan ke list lain tanpa diubah.
forEach Tidak Bisa Dihentikan di Tengah Jalan
Kamu tidak bisa menulis break di dalam forEach, dan return hanya mengakhiri callback untuk item itu saja — element sisanya tetap diproses. Untuk loop yang ingin kamu hentikan begitu element yang dicari ketemu, pakai for...of dengan break.
Menerima Pengecekan sebagai Function — filter Buatan Sendiri dan Boolean
Misalnya kamu ingin menyaring sebuah list dengan kondisi yang berganti-ganti: pertama hanya produk yang ada stoknya, lalu hanya produk seharga 3000 yen ke atas. Kalau kamu menulis loop terpisah untuk tiap kondisi, hasilnya salinan yang nyaris identik, sampai ke array penampung hasil dan baris push-nya.
Yang berubah hanya baris berisi kondisi, jadi terima bagian itu sebagai function pengecekan, lalu kumpulkan hanya element yang membuatnya mengembalikan true. Apa yang diteruskan ke callback, dan dengan urutan apa, ditentukan oleh function yang menerima dan memanggilnya. filterItems di bawah hanya meneruskan satu element ke function pengecekan.
const products = [
{ name: "Alas meja", stock: 4 },
{ name: "Stand monitor", stock: 0 },
{ name: "Kotak kabel", stock: 9 },
];
// Menerima function pengecekan dan hanya mengumpulkan element yang hasilnya true
function filterItems(items, judge) {
const kept = [];
items.forEach((item) => {
if (judge(item)) {
kept.push(item);
}
});
return kept;
}
const inStock = filterItems(products, (product) => product.stock > 0);
console.log(inStock.length); // 2
console.log(inStock[0].name); // Alas meja
true, produk dengan stok 0 mendapat false, dan hanya yang pertama masuk ke kept. Element disimpan atau tidak ditentukan oleh return value function yang kamu teruskan.Kata "stok" sama sekali tidak muncul di dalam filterItems. Kalau nanti kamu hanya ingin produk yang stoknya banyak, cukup ubah pengecekan di baris pemanggilnya menjadi product.stock >= 5, tanpa menyentuh filterItems sama sekali.
Meneruskan Pengecekan yang Kamu Buat — Higher-Order Function yang Mengembalikan Function
Kadang pengecekan yang kamu teruskan ke filterItems hanya berbeda di kategori pembandingnya: "hanya alat tulis", "hanya peralatan makan", dan seterusnya. Kalau ekspresi pengecekannya ditulis ulang di setiap pemanggilan, mengganti nama property yang dibandingkan berarti mengedit semua pemanggilan itu.
Bentuk lain higher-order function adalah function yang mengembalikan function. Bentuknya sama dengan function factory di artikel sebelumnya: kamu membuat byCategory, yang menerima kategori dan mengembalikan function pengecekan. Pengecekan yang dikembalikan mengingat category sebagai closure, jadi bisa langsung diteruskan ke filterItems.
const items = [
{ name: "Buku tulis", category: "Alat tulis" },
{ name: "Mug", category: "Peralatan makan" },
{ name: "Pulpen", category: "Alat tulis" },
];
// Menerima kategori dan mengembalikan function yang mengecek apakah item termasuk kategori itu
function byCategory(category) {
return (item) => item.category === category;
}
// Belum ada yang dibandingkan; yang dikembalikan adalah function pengecekan
console.log(typeof byCategory("Alat tulis")); // function
// Teruskan pengecekan yang dikembalikan langsung ke filterItems dari bagian sebelumnya
console.log(filterItems(items, byCategory("Alat tulis")).length); // 2
console.log(filterItems(items, byCategory("Peralatan makan")).length); // 1
filterItems(items, byCategory("Alat tulis"))— membuat pengecekan lalu meneruskannya sebagai argument ke-2
category— berisi "Alat tulis" dari baris yang memanggilnyareturn (item) => ...— mengembalikan function pengecekan tanpa membandingkan apa pun
item— berisi satu element yang diteruskan darifilterItemsitem.category === category— membandingkan dengan category yang diingat
judge— berisi function pengecekan yang dikembalikanbyCategoryjudge(item)— meneruskan element satu per satu dan memanggil pengecekannya
category ditentukan di baris yang memanggil byCategory, dan item ditentukan di dalam filterItems. Tempat pengecekan dibuat dan tempat pengecekan dipanggil itu berbeda.Baris yang menjalankan byCategory("Alat tulis") belum membandingkan apa pun; perbandingan baru terjadi saat judge(item) dipanggil di dalam filterItems. Dengan 3 produk, pengecekan yang sama dipanggil 3 kali, dan category tetap "Alat tulis" sementara hanya item yang berganti.
Tanpa Tanda Kurung, Semua Item Tetap Tersimpan
Kalau kamu menulis filterItems(items, byCategory), setiap produk diteruskan ke byCategory sebagai category, dan yang kembali sebagai hasil pengecekan adalah sebuah function. Function bersifat truthy (dianggap true di dalam kondisi), jadi semua produk tetap tersimpan. Yang seharusnya kamu teruskan adalah pengecekan yang dikembalikan byCategory("Alat tulis").
Satu Kerangka untuk Semua — Function Umum dengan Logika yang Bisa Ditukar
Misalnya kamu menulis dua function untuk menjumlahkan ongkos kirim, satu untuk pengiriman reguler dan satu untuk ekspres. Loop dan variabel penampung totalnya sama; yang berbeda hanya ekspresi untuk menghitung ongkos kirim tiap pesanan. Dengan susunan seperti ini, mudah sekali memperbaiki yang satu tapi lupa memperbaiki yang lain.
Jadikan bagian yang berbeda sebagai parameter berupa function, sehingga kerangkanya cukup satu. sumShipping menerima array pesanan dan function yang menghitung ongkos kirim, meneruskan pesanan satu per satu ke function itu, lalu menjumlahkan return value-nya. Sisi pemanggil hanya menulis ekspresi ongkos kirimnya, dan sisi penerima hanya memegang loop dan totalnya.
const shippingOrders = [{ id: "A-1101", total: 6200 }, { id: "A-1102", total: 3400 }];
// Kerangkanya satu. Hanya cara menghitung ongkos kirim yang diterima sebagai function
function sumShipping(orders, calcFee) {
let total = 0;
for (const order of orders) {
total = total + calcFee(order);
}
return total;
}
const normalFee = (order) => (order.total >= 5000 ? 0 : 500);
const expressFee = () => 800; // Tidak memakai order, jadi parameternya boleh tidak ditulis
const flatFee = () => 300;
console.log(sumShipping(shippingOrders, normalFee)); // 500
console.log(sumShipping(shippingOrders, expressFee)); // 1600
console.log(sumShipping(shippingOrders, flatFee)); // 600
calcFee, total dua pesanan yang sama menjadi 500, 1600, atau 600. Kode di dalam sumShipping sama persis di ketiga pemanggilan.Bahkan perubahan seperti memberi batas atas pada total cukup dilakukan dengan satu edit di sumShipping, dan berlaku untuk ketiga jenis ongkos kirim. Tabel di bawah menunjukkan bagaimana ketiga function penerima di artikel ini memanggil callback-nya dan memakai return value-nya.
| Function penerima | Cara memanggil callback | Return value callback |
|---|---|---|
| forEach | Dipanggil per element | Tidak dipakai |
| filterItems buatan sendiri | judge(item) per element | Element yang true dikumpulkan |
| sumShipping buatan sendiri | calcFee(order) per pesanan | Ditambah ke total ongkos kirim |
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Di filterItems buatan sendiri, apa yang terjadi pada element saat function pengecekan yang kamu teruskan mengembalikan false?
Soal 3Kalau kamu meneruskan byCategory tanpa tanda kurung, seperti filterItems(items, byCategory), apa isi array yang dikembalikan?