Belajar dengan membaca secara berurutan

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 yang Sama Dipanggil Tiga Kali
Item ke-1:A-1101order berisiA-1101Menampilkan A-1101Buku tulis 480 yenItem ke-2:A-1102order berisiA-1102Menampilkan A-1102Pulpen 150 yenItem ke-3:A-1103order berisiA-1103Menampilkan A-1103Sticky note320 yen
Kamu hanya meneruskan satu function, 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.

Tampilkan daftar tiket support satu per satu. tickets sudah dideklarasikan.

① Tulis function printTicket yang menampilkan satu tiket dalam bentuk "[T-01] Cek status pengiriman (selesai)". Kalau belum selesai, tampilkan (belum) sebagai gantinya.

② Teruskan printTicket ke forEach untuk menampilkan semua tiket.

③ Hitung tiket yang sudah selesai, lalu tampilkan jumlahnya dalam bentuk "Selesai: N".

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
Return Value Menentukan Element Disimpan atau Tidak
Alas mejadengan stok 4Mengecekstock > 04 > 0 hasilnyatrueMasuk kekeptStand monitordengan stok 0Mengecekstock > 00 > 0 hasilnyafalseTidak masukke kept
Produk dengan stok 4 mendapat 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.

Dari jadwal pengiriman, hitung pengiriman yang memenuhi kondisi tertentu. shipments sudah dideklarasikan.

① Tulis countIf, yang menerima function pengecekan dan mengembalikan jumlah element yang memenuhi kondisinya.

② Tampilkan jumlah pengiriman yang dikirim di hari yang sama.

③ Tampilkan jumlah pengiriman dengan ongkos kirim 4000 yen ke atas.

④ Simpan function yang mengecek pengiriman yang tidak dikirim di hari yang sama ke variabel isNextDay, lalu teruskan ke countIf dan tampilkan jumlahnya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
Di Mana Value Pengecekan Ditentukan
Level terluar (tidak di dalam kurung kurawal mana pun)
  • filterItems(items, byCategory("Alat tulis")) — membuat pengecekan lalu meneruskannya sebagai argument ke-2
Di dalam byCategory
  • category — berisi "Alat tulis" dari baris yang memanggilnya
  • return (item) => ... — mengembalikan function pengecekan tanpa membandingkan apa pun
Di dalam function pengecekan yang dikembalikan
  • item — berisi satu element yang diteruskan dari filterItems
  • item.category === category — membandingkan dengan category yang diingat
Di dalam filterItems
  • judge — berisi function pengecekan yang dikembalikan byCategory
  • judge(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").

Saring pesanan yang berhak mendapat gratis ongkos kirim khusus member. orders dan filterItems sudah dideklarasikan.

① Tulis both, yang menerima dua pengecekan dan mengembalikan pengecekan yang bernilai true hanya jika keduanya terpenuhi.

② Simpan dua pengecekan ke variabel: satu untuk pesanan 5000 yen ke atas, dan satu untuk member.

③ Gabungkan pengecekan dari ② dengan both, teruskan hasilnya ke filterItems, lalu tampilkan ID setiap pesanan yang tersisa.

④ Jalankan pengecekan dari ③ pada pesanan kedua, lalu tampilkan hasilnya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

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
Tiga Function Ongkos Kirim, Satu Kerangka
Hanya satu:sumShipping(orders,calcFee)calcFee diisinormalFeecalcFee diisiexpressFeecalcFee diisiflatFee0 kalau 5000 yenke atas, selainitu 500800 apa punpesanannya300 apa punpesanannyaTotalnya 500Totalnya 1600Totalnya 600
Tergantung function yang diteruskan sebagai 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 penerimaCara memanggil callbackReturn value callback
forEachDipanggil per elementTidak dipakai
filterItems buatan sendirijudge(item) per elementElement yang true dikumpulkan
sumShipping buatan sendiricalcFee(order) per pesananDitambah ke total ongkos kirim

Dari catatan shift pekerja paruh waktu, tampilkan upah tiap shift lalu hitung totalnya. shifts sudah dideklarasikan.

① Tulis payReport, yang menerima catatan shift dan function penghitung upah, menampilkan tiap shift dalam bentuk "N jam: N yen", lalu mengembalikan totalnya.

② Teruskan perhitungan dengan upah 1100 yen per jam, lalu tampilkan total yang dikembalikan dalam bentuk "Total N yen".

③ Teruskan perhitungan yang membuat hanya shift malam dibayar 1,25 kali lipat, lalu tampilkan totalnya dalam bentuk yang sama.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Saat kamu menulis orders.forEach(printOrder), siapa yang memanggil printOrder?

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?