Soal 1Apa yang diterima argument pertama callback yang kamu berikan ke reduce?
reduce — Merangkum Array Menjadi 1 Value
Pelajari reduce yang merangkum array menjadi 1 value: accumulator dan initial value, total, nilai maksimum, grouping, sampai TypeError di array kosong.
Misalnya kamu perlu menghitung total dari rincian sebuah pesanan. map tidak mengubah jumlah element dan filter hanya membuang sebagian, jadi keduanya tetap mengembalikan array. Proses yang merangkum array menjadi 1 value, seperti total atau nilai maksimum, tidak bisa ditulis dengan kedua method ini.
Artikel ini membahas reduce, yang merangkum array menjadi 1 value, beserta initial value yang menjadi titik awalnya.
Merangkum Array Menjadi 1 Value — reduce dan Initial Value
Misalnya kamu ingin menampilkan total harga di keranjang belanja. Kalau ditulis dengan forEach, variable penampung total harus berada di luar loop, sehingga baris mana pun sesudahnya bisa menimpanya. Karena value-nya berubah setiap kali ditambah, variable itu juga harus dideklarasikan dengan let, bukan const.
Method array reduce (method yang meneruskan element ke callback satu per satu dan hanya mengembalikan return value terakhir) memakai callback yang parameter pertamanya adalah accumulator (parameter yang berisi return value dari pemanggilan sebelumnya). Berbeda dengan map dan filter, element-nya ada di posisi kedua. Argument kedua untuk reduce sendiri adalah initial value (value yang dipegang accumulator pada pemanggilan pertama).
const cartItems = [
{ name: "Stand Laptop", price: 3200 },
{ name: "USB Hub", price: 2480 },
{ name: "Kabel HDMI", price: 1180 },
];
// Parameter pertama adalah return value pemanggilan sebelumnya, yang kedua element saat ini
const addPrice = (total, item) => total + item.price;
// Argument kedua, 0, menjadi total pada pemanggilan pertama
const totalPrice = cartItems.reduce(addPrice, 0);
console.log(totalPrice); // 6860
// Yang dikembalikan adalah 1 value, bukan array
console.log(typeof totalPrice); // number
total di pemanggilan ke-2. 6860 yang dikembalikan pemanggilan terakhir adalah hasil reduce.Kamu tidak perlu menyiapkan variable sendiri untuk menyimpan hasil sementara, karena reduce yang bertugas meneruskan value dari satu pemanggilan ke pemanggilan berikutnya. Yang diteruskan juga tidak harus number; bisa juga element atau object.
Menyimpan yang Lebih Besar — Meneruskan Number atau Element
Misalnya kamu ingin tahu video mana yang paling banyak ditonton di antara video yang sudah kamu unggah. Kali ini tidak ada yang dijumlahkan; kamu membandingkan value dan hanya meneruskan yang lebih besar ke pemanggilan berikutnya. Yang perlu kamu putuskan adalah apa yang dipegang accumulator di pemanggilan pertama.
Accumulator bisa berisi number maupun element itu sendiri. Kalau argument kedua diisi 0, yang diteruskan dari satu pemanggilan ke pemanggilan berikutnya adalah number; kalau diisi videos[0], yang diteruskan adalah element itu sendiri. Pakai initial value yang jenisnya sama dengan yang dikembalikan callback.
const videos = [
{ title: "Dasar SQL #1", views: 8200 },
{ title: "Dasar Linux #3", views: 15400 },
{ title: "Pengenalan Git", views: 9700 },
];
// Initial value-nya number, jadi yang dibandingkan dan dikembalikan juga number
const maxViews = videos.reduce((max, video) => Math.max(max, video.views), 0);
console.log(maxViews); // 15400
// Initial value-nya element, jadi yang dibandingkan dan dikembalikan juga element
const topVideo = videos.reduce(
(top, video) => (video.views > top.views ? video : top),
videos[0],
);
console.log(topVideo.title); // Dasar Linux #3
Kedua pemanggilan reduce sama-sama membandingkan views; bedanya hanya apa yang diteruskan. Kalau initial value di baris kedua diganti 0, top.views di pemanggilan pertama bernilai undefined, sehingga perbandingannya selalu false dan yang dikembalikan tetap 0 sampai akhir.
Initial Value 0 Tidak Cocok untuk Bilangan Negatif
Di array yang bisa berisi value negatif, seperti catatan suhu, kalau kamu mulai dari 0, maka 0 tetap dianggap lebih besar walaupun nilai maksimum sebenarnya -3, dan hasilnya jadi value yang tidak ada di array. Kalau yang dibandingkan adalah sesama element dan initial value-nya element pertama array, rentang value-nya tidak lagi berpengaruh.
Merangkum Jumlah ke dalam Object — Grouping
Misalnya kamu ingin tahu ada berapa tiket support di tiap kategori. Berbeda dengan total, hasilnya bukan 1 number, melainkan 1 value untuk tiap kategori. Kamu bisa menyiapkan object kosong di luar lalu menambah hitungannya dengan forEach, tapi object penghitung itu jadi terpisah dari prosesnya.
{} juga bisa dipakai sebagai initial value. Dalam hal ini accumulator-nya adalah object yang sedang dihitung, dan callback memperbarui sebuah key lalu mengembalikan object itu. Key yang belum ada bernilai undefined saat dibaca, jadi sisipkan ?? 0 supaya hitungannya mulai dari 0.
const tickets = [
{ subject: "Saya ingin mengembalikan barang", category: "Retur" },
{ subject: "Pesanan saya belum sampai", category: "Pengiriman" },
{ subject: "Tolong terbitkan ulang label pengiriman", category: "Pengiriman" },
];
// Tambahkan 1 ke hitungan kategori tiap tiket
const countByCategory = tickets.reduce((counts, ticket) => {
counts[ticket.category] = (counts[ticket.category] ?? 0) + 1;
return counts; // Teruskan counts yang sudah diperbarui ke pemanggilan berikutnya
}, {}); // {} ini menjadi counts di pemanggilan pertama
console.log(JSON.stringify(countByCategory)); // {"Retur":1,"Pengiriman":2}
console.log(countByCategory["Pengiriman"]); // 2
counts yang diterimanya, lalu mengembalikan object yang sama. Satu-satunya object yang diubah adalah object yang diberikan sebagai initial value.{} di initial value adalah object baru yang hanya menampung hasil hitungan, jadi element di tickets tidak diubah sama sekali. Tabel di bawah merangkum pasangan initial value dan return value callback untuk ketiga cara pakai yang sudah dibahas.
| Yang ingin dihasilkan | Initial value | Yang dikembalikan callback |
|---|---|---|
| Total | 0 | Total sebelumnya ditambah value-nya |
| Element terbesar | Element pertama array | Element yang lebih besar |
| Jumlah per kategori | Object kosong | Object dengan key yang sudah diperbarui |
Kalau Tidak Ada Element Sama Sekali — reduce Tanpa Initial Value
Hasil penyaringan bisa saja kosong. Misalnya kamu menghitung total pengiriman hari ini pada hari yang tidak ada pengirimannya sama sekali: tergantung cara menulisnya, reduce bisa mengembalikan 0 atau malah berhenti karena error.
Argument kedua, yaitu initial value, boleh tidak ditulis. Kalau tidak ditulis, element pertama array menjadi accumulator awal, dan callback pertama kali dipanggil dengan element di index 1. Array kosong tidak punya element pertama untuk diambil, jadi yang terjadi adalah TypeError.
const yesterdayAmounts = [3200, 2480, 1180];
const todayAmounts = [];
// Tanpa initial value, element pertama, 3200, menjadi total awal
console.log(yesterdayAmounts.reduce((total, amount) => total + amount)); // 6860
// Di array kosong pun, initial value dikembalikan apa adanya
console.log(todayAmounts.reduce((total, amount) => total + amount, 0)); // 0
// Tanpa initial value, total awal tidak bisa ditentukan
// todayAmounts.reduce((total, amount) => total + amount);
// TypeError: Reduce of empty array with no initial value
Bug seperti ini sulit disadari saat kodenya ditulis: dengan data yang ada, kodenya berjalan normal, dan baru berhenti pada hari pertama ketika tidak ada satu element pun yang cocok. Kalau hasil filter diteruskan ke reduce, berikan initial value untuk berjaga-jaga kalau hasilnya kosong.
Tanpa {}, Element Pertama yang Ikut Diubah
Kalau {} di akhir contoh countByCategory dihapus, object tiket tickets[0] itu sendiri menjadi counts awal. Tidak ada error: object itu malah ditambahi key Pengiriman, dan 1 tiket Retur tidak pernah terhitung. Walaupun array-nya tidak kosong, selalu berikan initial value saat merangkum ke dalam object.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
Soal 2Kalau reduce yang membandingkan views antar-element lalu mengembalikan yang lebih besar diberi initial value 0, apa yang dikembalikan?
Soal 3Apa yang terjadi kalau kamu memanggil reduce tanpa initial value pada array kosong?