Front-End & Back-End Development 2

1.3 Async/Await & Promises

Pernahkah Anda membuka aplikasi belanja online dan menunggu data produk muncul setelah loading sebentar? Atau menekan tombol “Submit” dan aplikasi tetap bisa digunakan tanpa harus hang?
Semua itu terjadi karena JavaScript mampu menjalankan kode secara asinkron (asynchronous) artinya, JavaScript dapat melanjutkan pekerjaan lain tanpa harus menunggu satu proses selesai dulu.Inilah yang membuat backend dan API calls bisa berjalan cepat, efisien, dan responsif.
Dua fitur utama yang membantu hal ini adalah Promises dan Async/Await.

1.3.1 Apa Itu Async?

Dalam pemrograman, ada dua cara kerja utama:
Synchronous (Sync):
Semua proses dijalankan berurutan, satu selesai baru lanjut berikutnya.
console.log("1. Mulai");
console.log("2. Proses"); // Tunggu sampai selesai
console.log("3. Selesai");

// Output (berurutan):
// 1. Mulai
// 2. Proses
// 3. Selesai

Asynchronous (Async):

Proses bisa jalan bersamaan tanpa harus menunggu yang lain selesai.
console.log("1. Mulai");

setTimeout(() => {
  console.log("2. Proses (delay 2 detik)");
}, 2000);

console.log("3. Selesai");


// Output (nggak berurutan!):
// 1. Mulai
// 3. Selesai
// ... 2 detik kemudian ...
// 2. Proses (delay 2 detik)

Analogi sederhana:

Synchronous itu seperti antri di ATM, harus tunggu orang di depan selesai dulu.

Asynchronous itu seperti pesan makanan online, kita bisa lanjut aktivitas lain sambil menunggu notifikasi pesanan datang.

1.3.2 Promises

Promise = Janji bahwa operasi async akan selesai (atau gagal) di masa depan.
Promise adalah objek yang merepresentasikan hasil dari operasi asinkron bisa sukses (fulfilled) atau gagal (rejected).

// Buat Promise
const fetchProduct = new Promise((resolve, reject) => {
  setTimeout(() => {
    const product = { name: "Vitamin C", price: 85000 };

    // Simulate success
    resolve(product);

    // Atau simulate error:
    // reject(new Error("Product not found"));
  }, 1000);
});


Promise di atas seperti “janji” bahwa data produk akan dikirim setelah 1 detik, untuk menggunakan hasilnya, kita bisa menangkap janji tersebut dengan .then() dan .catch():

// Consume Promise

fetchProduct

  .then((product) => {

    console.log("Success:", product);

  })

  .catch((error) => {

    console.error("Error:", error.message);

  });


Promise punya tiga “status kehidupan”:
  1. Pending → Masih menunggu hasil
  2. Fulfilled → Selesai dengan sukses (resolve)
  3. Rejected → Gagal dengan error (reject)
Async/Await - Promises dengan Syntax Lebih Clean!
Kalau menulis banyak .then() terasa membingungkan, ES8 memperkenalkan async/await agar kode terlihat seperti synchronous, tapi tetap berjalan asynchronous di belakang layar.

Old Way (Promise Chaining):

function getProductDetails(productId) {
  fetchProduct(productId)
    .then((product) => {
      return fetchReviews(product.id);
    })
    .then((reviews) => {
      return fetchManufacturer(reviews.manufacturerId);
    })
    .then((manufacturer) => {
      console.log("Done!", manufacturer);
    })
    .catch((error) => {
      console.error("Error:", error);
    });
}

New Way (Async/Await):

async function getProductDetails(productId) {
  try {
    const product = await fetchProduct(productId);
    const reviews = await fetchReviews(product.id);
    const manufacturer = await fetchManufacturer(reviews.manufacturerId);

    console.log("Done!", manufacturer);
  } catch (error) {
    console.error("Error:", error);
  }
}


Hasilnya tetap sama, tapi jauh lebih mudah dibaca dan terstruktur seperti alur berpikir manusia.

Contoh Real: Fetch dari API

async function getProduct(id) {
  try {
    // Simulasi database query (async operation)
    const product = await Product.findById(id);

    if (!product) {
      throw new Error("Product not found");
    }

    return {
      success: true,
      data: product,
    };
  } catch (error) {
    return {
      success: false,
      error: error.message,
    };
  }
}

// Usage
const result = await getProduct(123);
if (result.success) {
  console.log("Product:", result.data);
} else {
  console.error("Error:", result.error);
}

Fitur await hanya bisa digunakan di dalam fungsi yang dideklarasikan dengan async.

1.3.3 Parallel vs Sequential Async

Sequential (berurutan) → cocok jika hasil pertama dibutuhkan oleh proses berikutnya.

async function fetchAllData() {
  const products = await fetchProducts(); // 1s
  const users = await fetchUsers(); // 1s
  const reviews = await fetchReviews(); // 1s

  return { products, users, reviews };
}
// Total: 3 detik (tunggu satu-satu)

Parallel (bersamaan) → cocok jika operasi tidak saling bergantung.

async function fetchAllData() {
  const [products, users, reviews] = await Promise.all([
    fetchProducts(), // Semua jalan bersamaan
    fetchUsers(),
    fetchReviews(),
  ]);

  return { products, users, reviews };
}
// Total: 1 detik (yang paling lama)

Kapan Pakai Mana?
  • Sequential: Kalau operation kedua butuh result dari operation pertama
  • Parallel: Kalau operation-nya independent (tidak saling tunggu)

1.3.4 Async/Await untuk Database Queries

Tujuan:

Mengimplementasikan async function untuk mengambil data produk dari database dengan penanganan kesalahan yang tepat.

Langkah-langkah Coding:

1. Struktur fungsi:

async function getAllProducts(category) {
  try {
    let query = {};

    if (category) {
      query.category = category;
    }

    const products = await Product.find(query);

    return {
      success: true,
      count: products.length,
      data: products,
    };
  } catch (error) {
    return {
      success: false,
      error: error.message,
    };
  }
}

2. Pengujian Fungsi oleh Aiman

// Get all
const all = await getAllProducts();
console.log(all);

// Get by category
const vitamins = await getAllProducts("Vitamin");
console.log(vitamins);

3. Implementasi parallel fetching oleh Aila:

async function getDashboardData() {
  const [products, users, reviews] = await Promise.all([
    getAllProducts(),
    getAllUsers(),
    getAllReviews(),
  ]);

  return { products, users, reviews };
}

Hasil Akhir

Fungsi-fungsi asinkron yang siap digunakan dalam lingkungan produksi, dengan eksekusi lebih cepat melalui Promise.all untuk operasi yang tidak saling bergantung.

Tips:
  • Selalu gunakan try...catch saat bekerja dengan async/await.
  • Gunakan Promise.all untuk menjalankan operasi paralel.
  • Struktur fungsi asinkron yang baik membantu menjaga keterbacaan dan stabilitas kode.

Kesimpulan

Kemampuan JavaScript dalam menjalankan proses secara asinkron memungkinkan aplikasi tetap responsif meskipun ada operasi yang memerlukan waktu lama, seperti pengambilan data dari API atau database. Dengan Promises dan Async/Await, pengembang dapat mengelola proses asinkron dengan cara yang lebih efisien, rapi, dan mudah dibaca.
Promises memberikan cara standar untuk menangani hasil yang datang di masa depan, sementara Async/Await menyederhanakan sintaks sehingga alurnya terlihat seperti kode sinkron. Dengan memahami dua konsep ini, Anda dapat menulis kode backend yang lebih cepat, tangguh, dan terhindar dari callback hell.

1.4 Object Oriented Programing Basics

Sebelum masuk lebih jauh, mari kita bayangkan sebuah skenario nyata. Anda sedang membangun aplikasi besar yang terdiri atas berbagai fitur, mulai dari pengelolaan produk, data pengguna, hingga proses transaksi. Ketika proyek bertambah besar, Anda tentu membutuhkan cara agar data dan fungsi tidak tersebar tanpa arah. Tanpa struktur yang baik, aplikasi Anda akan sulit dikembangkan dan rawan menimbulkan kesalahan. Di sinilah Object-Oriented Programming (OOP) menjadi pendekatan penting.

Object-Oriented Programming (OOP) adalah paradigma pemrograman yang membantu Anda menulis kode secara lebih terorganisir dengan mengelompokkan data (properties) dan perilaku (methods) ke dalam satu wadah yang disebut object. Pendekatan ini meniru cara kita melihat dunia nyata: setiap objek memiliki karakteristik dan tindakan. Dengan demikian, kode menjadi lebih rapi, mudah dipahami, dan fleksibel untuk dikembangkan.

1.4.1 Sebelum dan Sesudah Menggunakan OOP

Untuk memahami manfaatnya, berikut gambaran perbedaannya:

Tanpa OOP (Kode Tersebar dan Sulit Dikelola)

  • Data dan fungsi sering berada di berbagai file atau variabel terpisah.
  • Tidak ada hubungan jelas antara state dan behavior.
  • Perubahan kecil bisa berdampak ke banyak bagian kode.
  • Duplikasi kode lebih sering terjadi karena tidak ada struktur untuk menampung pola tertentu.
Contohnya, warna mobil, merk mobil, fungsi menyalakan, dan fungsi membelok mungkin disimpan dalam variabel atau fungsi terpisah:

const warnaMobil = "Merah";
const merkMobil = "Toyota";
const tahunMobil = 2023;

function nyalakanMobil() {
  console.log("Mobil menyala! 🚗");
}

function belokMobil(arah) {
  console.log(`Mobil belok ke ${arah}`);
}

nyalakanMobil();
belokMobil("kanan");

Dengan OOP (Struktur Lebih Jelas dan Terorganisir)

Dengan OOP, Anda menyimpan data dan perilaku dalam satu objek sehingga lebih mudah dilihat, dipahami, dan direvisi.

const mobil = {
  // Properties
  warna: "Merah",
  merk: "Toyota",
  tahun: 2023,

  // Methods
  nyalakan() {
    console.log("Mobil menyala! 🚗");
  },

  belok(arah) {
    console.log(`Mobil belok ke ${arah}`);
  }
};

// Usage
mobil.nyalakan(); // "Mobil menyala! 🚗"
mobil.belok("kanan"); // "Mobil belok ke kanan"


Dengan pendekatan ini, semua informasi tentang mobil berada dalam satu objek yang utuh. Anda tidak perlu mencari data atau fungsi ke berbagai tempat karena semuanya sudah terkelompok dengan jelas.

Melalui contoh di atas, Anda dapat melihat bahwa OOP membantu Anda mengelola proyek secara lebih sistematis. OOP menghubungkan data dan perilaku dalam satu unit, sehingga kode menjadi lebih rapi, mudah dipelihara, dan jauh lebih siap untuk skala aplikasi yang lebih besar.

Procedural vs OOP

Sebelum OOP, banyak program ditulis secara prosedural, di mana data dan fungsi berdiri terpisah. Misalnya:

Procedural Programming (Old Way):

// Data dan functions terpisah
const productName = "Vitamin C";
const productPrice = 85000;
const productStock = 50;

function calculateTotal(price, quantity) {
  return price * quantity;
}

function checkStock(stock, quantity) {
  return stock >= quantity;
}

Cara ini berfungsi, tapi cepat membingungkan jika program makin besar.Dengan OOP, semua hal terkait produk bisa dikemas jadi satu class seperti modern way berikut:
Object-Oriented Programming (Modern Way):

// Data dan functions dalam satu object
class Product {
  constructor(name, price, stock) {
    this.name = name;
    this.price = price;
    this.stock = stock;
  }

  calculateTotal(quantity) {
    return this.price * quantity;
  }

  checkStock(quantity) {
    return this.stock >= quantity;
  }
}

// Usage (clean, self-contained)
const product = new Product("Vitamin C", 85000, 50);
const total = product.calculateTotal(5);
const available = product.checkStock(5);

Lebih rapi, bukan? Semua hal tentang produk berada dalam satu tempat. Konsep ini disebut Encapsulation pembungkusan data dan fungsi agar lebih teratur dan aman.

1.4.2 Empat Pilar OOP

OOP punya empat prinsip utama yang membentuk fondasi kuat pemograman  modern:
  1. Encapsulation – Menggabungkan data dan method dalam satu unit (class).
  2. Inheritance – Pewarisan properti dan method dari class induk ke class turunan.
  3. Polymorphism – Satu method bisa punya perilaku berbeda di class yang berbeda.
  4. Abstraction – Menyembunyikan detail kompleks, hanya menampilkan hal penting. 
Empat pilar inilah yang membuat kode lebih efisien, mudah dikelola, dan bisa digunakan ulang.


1.4.3 Class & Constructor

Class adalah blueprint atau cetakan untuk membuat objek baru.Sementara itu, constructor adalah fungsi khusus yang otomatis dijalankan saat objek dibuat.

Membuat Class Pertama

class Product {
  // Constructor - jalan saat new Product() dipanggil
  constructor(name, price, stock) {
    this.name = name; // Property
    this.price = price;
    this.stock = stock;
  }

  // Method
  getInfo() {
    return `${this.name} - Rp ${this.price}`;
  }

  // Method dengan parameter
  calculateTotal(quantity) {
    return this.price * quantity;
  }

  // Method untuk update
  updateStock(quantity) {
    this.stock -= quantity;
  }
}

// Create objects (instances)
const product1 = new Product("Vitamin C", 85000, 50);
const product2 = new Product("Vitamin D", 120000, 30);

console.log(product1.getInfo()); // "Vitamin C - Rp 85000"
console.log(product2.getInfo()); // "Vitamin D - Rp 120000" 

Penjelasan:
  • class Product → Define class
  • constructor() → Initialize properties saat object dibuat
  • this → Refer ke object yang sedang dibuat
  • new Product() → Create instance baru dari class

Analogi:
  • Class = Cetakan kue (blueprint)
  • Instance = Kue yang dibikin dari cetakan (actual object)
  • Constructor = Recipe untuk bikin kue

Static Methods & Properties

Kadang kita butuh fungsi yang tidak tergantung pada objek tertentu cukup pada class-nya.Itulah gunanya static method.
Static = Method/property yang belong to class, bukan instance.

class MathHelper {
  static PI = 3.14159;

  static square(x) {
    return x * x;
  }

  static circleArea(radius) {
    return this.PI * this.square(radius);
  }
}

// Call tanpa create instance
console.log(MathHelper.PI);                 // 3.14159
console.log(MathHelper.square(5));         // 25
console.log(MathHelper.circleArea(10));    // 314.159

// const helper = new MathHelper(); //Nggak perlu!

Kapan Pakai Static?
  • Utility functions (nggak butuh instance data)
  • Constants
  • Factory methods
Selain itu, JavaScript juga punya getter dan setter, cara elegan untuk membaca dan menulis data dengan kontrol tambahan, seperti validasi harga tidak boleh negatif.

Getters & Setters

Kadang kita butuh fungsi yang tidak tergantung pada objek tertentu cukup pada class-nya. Itulah gunanya static method.
Getter = Method yang dipanggil kayak property (tanpa ())
Setter = Method untuk set value dengan validation

class Product {
  constructor(name, price) {
    this._name = name; // Underscore = convention untuk private
    this._price = price;
  }

  // Getter
  get name() {
    return this._name.toUpperCase();
  }

  get price() {
    return this._price;
  }

  get priceFormatted() {
    return `Rp ${this._price.toLocaleString("id-ID")}`;
  }

  // Setter dengan validation
  set price(value) {
    if (value < 0) {
      throw new Error("Price must be positive!");
    }
    this._price = value;
  }
}

const product = new Product("Vitamin C", 85000);

// Getter (dipanggil tanpa ())
console.log(product.name); // "VITAMIN C"
console.log(product.priceFormatted); // "Rp 85.000"

// Setter
product.price = 95000; // Valid
product.price = -1000; // Error: Price must be positive!

Membuat Product Class

Tujuan: Membuat class Product lengkap dengan constructor, methods, getters, dan setters.
Langkah-langkah Coding: 

1. Structure Class

2. Review dan refine:

class Product {
  constructor(name, price, stock, category) {
    this.name = name;
    this._price = price; // Underscore untuk private convention
    this.stock = stock;
    this.category = category;
  }

  getInfo() {
    return `${this.name} (${this.category}) - ${this.priceFormatted}`;
  }

  calculateDiscount(percentage) {
    return this._price * (1 - percentage / 100);
  }

  get priceFormatted() {
    return `Rp ${this._price.toLocaleString("id-ID")}`;
  }

  set price(value) {
    if (value <= 0) {
      throw new Error("Harga harus positif!");
    }
    this._price = value;
  }

  get price() {
    return this._price;
  }
}

3. Pengujian oleh Aiman dan Aila:

const vitamin = new Product("Vitamin C 1000mg", 85000, 50, "Vitamin");

console.log(vitamin.getInfo());
// "Vitamin C 1000mg (Vitamin) - Rp 85.000"

console.log(vitamin.calculateDiscount(10));
// 76500 (discount 10%)

vitamin.price = 95000; // Update via setter
console.log(vitamin.priceFormatted);
// "Rp 95.000"

Hasil Akhir:

Class Product yang rapih, mudah digunakan kembali, dan dapat digunakan untuk membuat banyak objek produk dengan struktur yang konsisten.

Tips:
  • Tambahkan validasi khusus melalui setter untuk menjaga konsistensi nilai.
  • Gunakan getter ketika membutuhkan nilai turunan seperti format harga.
  • Struktur class seperti ini memudahkan pengembangan fitur lanjutan pada aplikasi.

1.4.4 Inheritance & Extends

Inheritance = Class child inherit properties & methods dari parent class. Code reusability supreme!

Apa Itu Inheritance?

Inheritance memungkinkan class baru “mewarisi” properti dan method dari class lain. Misalnya, semua produk memiliki nama dan harga, tapi vitamin punya dosage, dan peralatan medis punya warranty.

Tanpa Inheritance:

class Vitamin {
  constructor(name, price, stock, dosage) {
    this.name = name;
    this.price = price;
    this.stock = stock;
    this.dosage = dosage;
  }
  // ... methods
}

class Supplement {
  constructor(name, price, stock, ingredients) {
    this.name = name; // Duplicate!
    this.price = price; // Duplicate!
    this.stock = stock; // Duplicate!
    this.ingredients = ingredients;
  }
  // ... methods
}

Banyak code duplicate!
Dengan Inheritance:

// Parent class (Base class)
class Product {
  constructor(name, price, stock) {
    this.name = name;
    this.price = price;
    this.stock = stock;
  }

  getInfo() {
    return `${this.name} - Rp ${this.price}`;
  }
}

// Child class extends Parent
class Vitamin extends Product {
  constructor(name, price, stock, dosage) {
    super(name, price, stock); // Call parent constructor
    this.dosage = dosage; // Additional property
  }

  // Additional method
  getDosageInfo() {
    return `${this.name} - Dosage: ${this.dosage}mg`;
  }
}

class Supplement extends Product {
  constructor(name, price, stock, ingredients) {
    super(name, price, stock);
    this.ingredients = ingredients;
  }

  getIngredients() {
    return this.ingredients.join(", ");
  }
}

// Usage
const vitC = new Vitamin("Vitamin C", 85000, 50, 1000);
console.log(vitC.getInfo()); // Inherited dari Product!
console.log(vitC.getDosageInfo()); // Method sendiri!

const omega = new Supplement("Omega-3", 200000, 25, ["Fish Oil", "EPA", "DHA"]);
console.log(omega.getInfo()); // Inherited!
console.log(omega.getIngredients()); // Method sendiri!

Keuntungan Inheritance:
  • Code reusability - Nggak nulis ulang yang sama
  • Maintainability - Update di parent, semua child ikut update
  • Logical structure - Hierarchy yang jelas
Analogi:
  • Parent Class (Product) = Ayah yang punya warisan (rumah, mobil)
  • Child Classes (Vitamin, Supplement) = Anak-anak yang inherit warisan + punya harta sendiri


Super Keyword


super digunakan untuk:
  • Call parent constructor → super(params)
  • Call parent methods → super.methodName()

class DiscountedProduct extends Product {
  constructor(name, price, stock, discountPercent) {
    super(name, price, stock); // Call parent constructor
    this.discountPercent = discountPercent;
  }

  // Override parent method
  getInfo() {
    const parentInfo = super.getInfo(); // Call parent method
    return `${parentInfo} (Discount: ${this.discountPercent}%)`;
  }

  getFinalPrice() {
    return this.price * (1 - this.discountPercent / 100);
  }
}

const product = new DiscountedProduct("Vitamin C", 85000, 50, 10);
console.log(product.getInfo());
// "Vitamin C - Rp 85000 (Discount: 10%)"

console.log(product.getFinalPrice());
// 76500

Method Overriding

Child class bisa override (replace) method dari parent:

class Product {
  getShippingCost() {
    return 10000; // Default shipping
  }
}

class BulkyProduct extends Product {
  // Override parent method
  getShippingCost() {
    return 50000; // Heavy item, more expensive shipping
  }
}

const vitamin = new Product("Vitamin C", 85000);
console.log(vitamin.getShippingCost()); // 10000

const equipment = new BulkyProduct("Blood Pressure Monitor", 350000);
console.log(equipment.getShippingCost()); // 50000 (overridden!)

Inheritance untuk Product Categories

Tujuan: Membuat hierarchy kelas Product dengan konsep inheritance untuk berbagai kategori produk.
Langkah-langkah Coding: 

1. Implementasi Base Class

class Product {
     constructor(name, price, stock) {
       this.name = name;
       this.price = price;
       this.stock = stock;
       this.createdAt = new Date();
     }

     getInfo() {
       return `${this.name} - Rp ${this.price.toLocaleString("id-ID")}`;
     }

     isAvailable() {
       return this.stock > 0;
     }
   }

2. Implementasi Vitamin Class

class Vitamin extends Product {
     constructor(name, price, stock, dosage) {
       super(name, price, stock);
       this.dosage = dosage;
       this.category = "Vitamin";
     }

     getDosageInfo() {
       return `Dosage: ${this.dosage}mg per serving`;
     }

     // Override getInfo
     getInfo() {
       return `${super.getInfo()} | ${this.getDosageInfo()}`;
     }
   }

3. Implementasi MedicalEquipment Class:

class MedicalEquipment extends Product {
  constructor(name, price, stock, warrantyYears) {
    super(name, price, stock);
    this.warrantyYears = warrantyYears;
    this.category = "Medical Equipment";
  }

  getWarrantyInfo() {
    return `Warranty: ${this.warrantyYears} years`;
  }

  getInfo() {
    return `${super.getInfo()} | ${this.getWarrantyInfo()}`;
  }
}

4. Test inheritance:

const vitC = new Vitamin("Vitamin C 1000mg", 85000, 50, 1000);
console.log(vitC.getInfo());
// "Vitamin C 1000mg - Rp 85.000 | Dosage: 1000mg per serving"

const thermometer = new MedicalEquipment("Termometer Digital", 75000, 60, 2);
console.log(thermometer.getInfo());
// "Termometer Digital - Rp 75.000 | Warranty: 2 years"

// Both inherit isAvailable() dari Product!
console.log(vitC.isAvailable()); // true
console.log(thermometer.isAvailable()); // true

Hasil Akhir:

Hierarchy kelas yang rapi dan mudah dikembangkan, dengan pemanfaatan ulang kode melalui inheritance. Struktur ini memudahkan penambahan kategori produk baru.

Tips:
  • Pastikan super() selalu dipanggil pertama kali dalam constructor kelas turunan.
  • Logika bisnis perlu ditambahkan secara manual.
  • Gunakan inheritance untuk membagikan perilaku umum antar kelas.

1.4.5 Encapsulation

Encapsulation juga bisa digunakan untuk melindungi data dari akses langsung.

JavaScript modern mendukung private fields menggunakan tanda #.
Encapsulation = Bundling data & methods + menyembunyikan detail internal.

Private Fields (ES2022)

JavaScript modern support private fields dengan prefix #:

class BankAccount {
  #balance = 0; // Private field (nggak bisa diakses dari luar)

  constructor(owner, initialBalance) {
    this.owner = owner;
    this.#balance = initialBalance;
  }

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
      return `Deposited: Rp ${amount}`;
    }
    return "Invalid amount";
  }

  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
      return `Withdrawn: Rp ${amount}`;
    }
    return "Insufficient funds or invalid amount";
  }

  getBalance() {
    return this.#balance; // Access via method only!
  }
}

const account = new BankAccount("Aiman", 1000000);

console.log(account.getBalance()); // 1000000
console.log(account.#balance); // Error: Private field!

account.deposit(500000);
console.log(account.getBalance()); // 1500000

// account.#balance = 9999999; // Nggak bisa! Protected!


Encapsulation with Getters/Setters

Sebelum ES2022 (sebelum ada #), kita pakai convention _ + getters/setters:

class Product {
  constructor(name, price, stock) {
    this._name = name;
    this._price = price;
    this._stock = stock;
  }

  // Getter
  get price() {
    return this._price;
  }

  // Setter dengan validation
  set price(value) {
    if (value < 0) {
      throw new Error("Price cannot be negative!");
    }
    if (value > 10000000) {
      throw new Error("Price too high! Check again.");
    }
    this._price = value;
  }

  get stock() {
    return this._stock;
  }

  set stock(value) {
    if (value < 0) {
      throw new Error("Stock cannot be negative!");
    }
    this._stock = value;
  }

  // Public method untuk controlled update
  sell(quantity) {
    if (quantity > this._stock) {
      return { success: false, message: "Insufficient stock" };
    }
    this._stock -= quantity;
    return { success: true, message: `Sold ${quantity} units` };
  }
}

const product = new Product("Vitamin C", 85000, 50);

// Use setters (dengan validation)
product.price = 95000; // Valid
// product.price = -1000; // Error!

// Controlled stock update
console.log(product.sell(5));
// { success: true, message: "Sold 5 units" }
console.log(product.stock);
// 45

1.4.6 Polymorphism

Polymorphism = "Banyak bentuk". Same method name, different implementations!

Method Overriding (Runtime Polymorphism)

class Product {
  calculateShipping() {
    return 10000; // Standard shipping
  }

  getDisplayInfo() {
    return `${this.name} - ${this.calculateShipping()}`;
  }
}

class LightProduct extends Product {
  calculateShipping() {
    return 5000; // Lighter, cheaper shipping
  }
}

class HeavyProduct extends Product {
  calculateShipping() {
    return 50000; // Heavier, expensive shipping
  }
}

class DigitalProduct extends Product {
  calculateShipping() {
    return 0; // Digital product, no shipping!
  }
}

// Same method, different results!
const vitamin = new LightProduct();
vitamin.name = "Vitamin C";
console.log(vitamin.getDisplayInfo()); // "Vitamin C - 5000"

const equipment = new HeavyProduct();
equipment.name = "Blood Pressure Monitor";
console.log(equipment.getDisplayInfo()); // "Blood Pressure Monitor - 50000"

const ebook = new DigitalProduct();
ebook.name = "Health Guide";

Magic Polymorphism:


// Function yang terima any Product type
function displayProductShipping(product) {
  console.log(
    `Shipping for ${product.name}: Rp ${product.calculateShipping()}`
  );
}

// Work dengan semua product types!
displayProductShipping(vitamin); // "Shipping for Vitamin C: Rp 5000"
displayProductShipping(equipment); // "Shipping for Blood Pressure Monitor: Rp 50000"
displayProductShipping(ebook); // "Shipping for Health Guide eBook: Rp 0"


One function, works dengan multiple types! Ini kekuatan polymorphism! 

Analogi:
  • Shape: Circle, Square, Triangle - semua punya method calculateArea()
  • Tapi implementation-nya beda (π×r² vs s² vs ½×base×height)
  • Function printArea(shape) work untuk semua shapes!

Polymorphic Shipping Calculator

Tujuan: Mengimplementasikan polymorphism untuk menghitung biaya pengiriman yang berbeda berdasarkan jenis produk.

Langkah-langkah Coding: 

1. Implement base dan child classes (sudah dicontohkan sebelumnya)

2. Aiman implementasi function processOrder:

function processOrder(product, quantity) {
  const subtotal = product.price * quantity;
  const shipping = product.calculateShipping();
  const total = subtotal + shipping;

  return {
    product: product.name,
    quantity,
    subtotal,
    shipping,
    total,
  };
}

3. Aila menguji dengan berbagai jenis produk:

const vitC = new LightProduct("Vitamin C", 85000, 50);
const monitor = new HeavyProduct("BP Monitor", 350000, 15);
const ebook = new FreeShippingProduct("eBook", 50000, 999);

console.log(processOrder(vitC, 2));
// { product: "Vitamin C", quantity: 2, subtotal: 170000, shipping: 5000, total: 175000 }

console.log(processOrder(monitor, 1));
// { product: "BP Monitor", quantity: 1, subtotal: 350000, shipping: 50000, total: 400000 }

console.log(processOrder(ebook, 1));
// { product: "eBook", quantity: 1, subtotal: 50000, shipping: 0, total: 50000 }

Hasil Akhir:

Satu fungsi (processOrder) dapat bekerja untuk berbagai tipe produk, masing-masing dengan biaya pengiriman berbeda sesuai implementasi metode polymorphic calculateShipping. Ini merupakan penerapan nyata polymorphism dalam pengembangan aplikasi.

Tips:
  • Polymorphism membuat kode lebih fleksibel dan dapat dikembangkan.
  • Menambah jenis produk baru sangat mudah tanpa perlu mengubah fungsi processOrder.

Kesimpulan

Object-Oriented Programming (OOP) membantu developer menulis kode yang lebih terstruktur, efisien, dan mudah dikembangkan. Dengan menyatukan data dan perilaku dalam class, OOP menyediakan pendekatan yang jelas untuk mengelola kompleksitas, terutama melalui konsep encapsulation, inheritance, polymorphism, dan abstraction.
Selain itu, OOP mendukung penggunaan ulang kode dan skalabilitas tinggi. Saat aplikasi berkembang, fitur baru dapat ditambahkan tanpa harus menulis ulang logika yang sudah stabil. Inilah alasan mengapa OOP menjadi fondasi penting dalam pengembangan backend modern, termasuk pada ekosistem JavaScript.

1.5 Design Patterns

Sebelum masuk ke contoh-contoh spesifik, mari kita pahami terlebih dahulu mengapa Design Patterns menjadi bagian penting dalam pengembangan perangkat lunak modern. Bayangkan Anda bekerja di beberapa proyek berbeda, namun berulang kali menemukan masalah yang serupa: alur logika yang mirip, struktur kode yang terasa repetitif, atau kesulitan dalam menjaga konsistensi antar modul. Kondisi ini biasa terjadi, dan justru menjadi tanda bahwa Anda membutuhkan pendekatan yang lebih matang.

Design Patterns hadir sebagai solusi. Design Patterns adalah proven solutions, yaitu solusi yang sudah diuji dan digunakan oleh banyak engineer untuk menangani masalah umum dalam desain perangkat lunak. Anda dapat menganggapnya sebagai “resep standar” dalam pemrograman. Dengan mengikuti resep tersebut, kode Anda menjadi lebih rapi, terstruktur, dan lebih mudah dikembangkan oleh tim maupun skala proyek yang lebih besar.

1.5.1 Mengapa Harus Menggunakan Design Patterns

Mengapa kita perlu menggunakan Design Patterns? Karena Design Patterns memberikan kerangka berpikir yang konsisten dan efisien untuk menyelesaikan masalah berulang. Alih-alih menciptakan solusi dari nol setiap kali menemui masalah, Anda dapat menggunakan pola yang sudah terbukti efektif sehingga proses pengembangan menjadi lebih cepat dan stabil. Berikut adalah kelebihan dan kekurangan Design Patterns yang perlu Anda pahami sebelum menerapkannya:


Kita akan meninjau tiga design pattern populer yang sering digunakan dalam pengembangan aplikasi modern:
  • Factory Pattern : untuk membuat objek kompleks dengan cara yang lebih efisien.
  • Singleton Pattern : memastikan hanya ada satu instance dari suatu class (biasanya untuk database atau konfigurasi).
  • Observer Pattern : memungkinkan satu objek memberitahu banyak objek lain ketika ada perubahan (sering digunakan dalam event system).

1.5.2 Factory Pattern – “Pabrik Pembuat Objek”

Bayangkan Anda punya pabrik yang bisa memproduksi berbagai jenis produk: vitamin, suplemen, hingga alat kesehatan. Setiap jenis produk memiliki struktur dan atribut berbeda. Jika setiap kali kita membuat produk harus menulis ulang logika yang berbeda, tentu tidak efisien.

Factory Pattern menyelesaikan hal ini dengan membuat satu “pabrik” yang bertugas memutuskan class mana yang perlu dibuat berdasarkan tipe produk yang diminta.

Perhatikan contoh berikut:

class Product {
  constructor(name, price, stock) {
    this.name = name;
    this.price = price;
    this.stock = stock;
  }
}

class Vitamin extends Product {
  constructor(name, price, stock, dosage) {
    super(name, price, stock);
    this.dosage = dosage;
    this.category = "Vitamin";
  }
}

class Supplement extends Product {
  constructor(name, price, stock, ingredients) {
    super(name, price, stock);
    this.ingredients = ingredients;
    this.category = "Supplement";
  }
}

class MedicalEquipment extends Product {
  constructor(name, price, stock, warranty) {
    super(name, price, stock);
    this.warranty = warranty;
    this.category = "Medical Equipment";
  }
}

// Factory Class
class ProductFactory {
  static createProduct(type, data) {
    switch (type) {
      case "vitamin":
        return new Vitamin(data.name, data.price, data.stock, data.dosage);

      case "supplement":
        return new Supplement(
          data.name,
          data.price,
          data.stock,
          data.ingredients
        );

      case "equipment":
        return new MedicalEquipment(
          data.name,
          data.price,
          data.stock,
          data.warranty
        );

      default:
        throw new Error(`Unknown product type: ${type}`);
    }
  }
}

// Usage - Clean dan simple!
const vitC = ProductFactory.createProduct("vitamin", {
  name: "Vitamin C 1000mg",
  price: 85000,
  stock: 50,
  dosage: 1000,
});

const thermometer = ProductFactory.createProduct("equipment", {
  name: "Termometer Digital",
  price: 75000,
  stock: 60,
  warranty: 2,
});

console.log(vitC); // Vitamin instance
console.log(thermometer); // MedicalEquipment instance

Penjelasan:
  • Product Factory bertugas menentukan class mana yang perlu dibuat berdasarkan type.
  • Developer cukup memanggil ProductFactory.createProduct(...) tanpa harus tahu detail tiap class.
  • Hasilnya: kode lebih bersih, terpusat, dan mudah dikembangkan jika nanti ada jenis produk baru.
Analogi :
Factory Pattern = pabrik mobil. Anda tinggal pilih jenisnya (SUV, Sedan, Truck), dan pabrik akan menghasilkan mobil yang sesuai.

1.5.3 Singleton Pattern – “Satu untuk Selamanya”

Ada kalanya kita hanya butuh satu objek saja yang digunakan di seluruh sistem  contohnya koneksi database atau konfigurasi aplikasi. Jika dibuat berulang kali, itu bisa membuang memori dan menyebabkan konflik.

Singleton Pattern memastikan hanya ada satu instance dari class tertentu.

class Database {
  static #instance = null; // Private static field
  #connection = null;

  // Private constructor (nggak bisa di-new dari luar!)
  constructor() {
    if (Database.#instance) {
      throw new Error("Use Database.getInstance() instead!");
    }

    this.#connection = "mongodb://localhost:27017";
    Database.#instance = this;
  }

  static getInstance() {
    if (!Database.#instance) {
      Database.#instance = new Database();
    }
    return Database.#instance;
  }

  getConnection() {
    return this.#connection;
  }
}

// Usage
const db1 = Database.getInstance();
const db2 = Database.getInstance();

console.log(db1 === db2); // true (same instance!)

// const db3 = new Database(); // Error!


Penjelasan:
  • Constructor Database hanya dijalankan satu kali.
  • Setiap kali getInstance() dipanggil, ia akan mengembalikan objek yang sama.
  • Sangat berguna untuk hal-hal seperti koneksi database, konfigurasi global, atau logger.
Use Cases:
  • Database connections
  • Configuration objects
  • Logger instances
  • Cache managers

1.5.4 Observer Pattern – “Pemberitahuan Otomatis”

Bagaimana jika kita ingin setiap kali stok produk berubah, sistem secara otomatis mengirimkan notifikasi ke email, SMS, dan dashboard?
Menulis kode notifikasi langsung di dalam class Product akan membuatnya sulit dirawat dan dikembangkan.

Observer Pattern hadir sebagai solusi. Ia memungkinkan satu objek (Subject) memberi tahu banyak “pengamat” (Observers) ketika ada perubahan.

class Subject {
  constructor() {
    this.observers = [];
  }

  subscribe(observer) {
    this.observers.push(observer);
  }

  unsubscribe(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer);
  }

  notify(data) {
    this.observers.forEach((observer) => observer.update(data));
  }
}

class Product extends Subject {
  constructor(name, price, stock) {
    super();
    this.name = name;
    this._price = price;
    this._stock = stock;
  }

  set stock(value) {
    this._stock = value;
    this.notify({ product: this.name, stock: value }); // Notify observers!
  }

  get stock() {
    return this._stock;
  }
}

// Observers
class EmailNotifier {
  update(data) {
    console.log(`📧 Email: ${data.product} stock is now ${data.stock}`);
  }
}

class SMSNotifier {
  update(data) {
    console.log(
      `SMS: Stock update for ${data.product}: ${data.stock} units`
    );
  }
}

class DashboardWidget {
  update(data) {
    console.log(
      `📊 Dashboard: Updating ${data.product} stock to ${data.stock}`
    );
  }
}

// Usage
const product = new Product("Vitamin C", 85000, 50);

const emailNotifier = new EmailNotifier();
const smsNotifier = new SMSNotifier();
const dashboard = new DashboardWidget();

// Subscribe observers
product.subscribe(emailNotifier);
product.subscribe(smsNotifier);
product.subscribe(dashboard);

// Update stock - all observers get notified!
product.stock = 45;

// Output:
// Email: Vitamin C stock is now 45
// SMS: Stock update for Vitamin C: 45 units
// Dashboard: Updating Vitamin C stock to 45

Penjelasan:
  • Subject menyimpan daftar observer.
  • Setiap kali stock berubah, method notify() dijalankan untuk memberi tahu semua observer.
  • Anda bisa menambah atau menghapus observer kapan pun tanpa mengubah logika utama.

1.5.5 Implement Observer Pattern

Tujuan: Membuat sistem notifikasi menggunakan Observer Pattern untuk perubahan stok produk.

Langkah-langkah Coding:

(Implementation sudah ditunjukkan di atas)

Pengujian:

const vitC = new Product("Vitamin C", 85000, 100);

const notifier = new EmailNotifier();
vitC.subscribe(notifier);

vitC.stock = 95; // Notifier gets called!
vitC.stock = 90; // Called again!

vitC.unsubscribe(notifier);
vitC.stock = 85; // Nggak notify lagi (already unsubscribed)

Hasil Akhir:
Sistem notifikasi menjadi fleksibel dan mudah dikembangkan. Berbagai jenis notifier dapat ditambahkan tanpa perlu mengubah class Product, berkat penggunaan Observer Pattern.

Tips:
  • Observer Pattern ideal untuk sistem berbasis event.
  • Pola ini banyak digunakan dalam pengelolaan state, termasuk pada ekosistem frontend modern

Kesimpulan

Design Patterns membantu developer menulis kode yang lebih efisien, terstruktur, dan mudah dikembangkan. Pola desain memungkinkan kita mengatasi berbagai masalah dengan pendekatan yang sudah teruji, sehingga kita tidak perlu membuat solusi dari nol setiap kali menghadapi situasi serupa.

Beberapa pola yang telah dipelajari, seperti Factory Pattern, Singleton Pattern, dan Observer Pattern, memiliki peran penting dalam menjaga arsitektur aplikasi tetap bersih dan scalable. Factory Pattern memudahkan pembuatan objek dengan variasi struktur, Singleton Pattern memastikan hanya ada satu instance untuk kebutuhan global, dan Observer Pattern memungkinkan pengiriman notifikasi otomatis saat terjadi perubahan pada objek. Dengan memahami dan menerapkan pola-pola tersebut, Anda memiliki fondasi kuat untuk membangun aplikasi modern yang rapi, efisien, dan mudah dikembangkan.

Mini Challenge

Selesaikan beberapa challenge berikut ini untuk mengukur pemahaman Anda mengenai beberapa materi yang sudah dipelajari dalam modul ini.

Challenge #1: Merge & Update Objects

Pada tantangan ini, Anda diminta membuat sebuah function yang mampu menggabungkan dua objek tanpa mengubah objek asli. Konsep ini disebut immutability, yaitu praktik menjaga data asli tetap utuh dan hanya membuat versi baru ketika perubahan diperlukan. Teknik ini sangat penting dalam JavaScript modern karena membantu mencegah bug yang sulit dilacak akibat perubahan data tersembunyi.

Tujuan function ini adalah menggabungkan originalProduct dengan updates, lalu mengembalikan objek baru yang sudah diperbarui.

Hint:

function updateProduct(originalProduct, updates) {
  return { ...originalProduct, ...updates };
}

Challenge #2: Error Handling Async

Dalam dunia nyata, pemanggilan API tidak selalu berjalan mulus. Koneksi dapat lambat, server dapat sibuk, atau respons dapat gagal. Tantangan ini membantu Anda memahami cara membuat mekanisme retry menggunakan async–await.

Gambaran singkatnya: Anda mencoba mengambil data sebanyak maksimal maxRetries kali. Jika masih gagal, barulah function melempar error.

Hint:

async function fetchProductWithRetry(id, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetchProduct(id);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1s
    }
  }
}

Challenge #3: Multi-Level Inheritance

Pada tantangan ini, Anda akan membuat hierarchy class menggunakan konsep inheritance dalam OOP. Tujuannya adalah memahami bagaimana satu class dapat menurunkan properties dan methods ke class lain dalam beberapa tingkatan.

Struktur hierarkinya adalah sebagai berikut:
  • Product → class dasar
  • HealthProduct → mewarisi Product, menambahkan expiryD
  • ate Vitamin → mewarisi HealthProduct, menambahkan dosage
  • Supplement → mewarisi HealthProduct, menambahkan ingredients
Cobalah untuk membuat semua class ini dan pastikan setiap level inheritance dapat digunakan dengan baik.

Challenge #4: Secure User Class

Keamanan data pengguna adalah hal penting dalam pengembangan aplikasi. Tantangan ini meminta Anda membuat class User dengan sistem penyimpanan password yang lebih aman melalui private fields (#password).

Anda perlu membuat:
  • Method login(inputPassword) untuk membandingkan input dengan password asli.
  • Method changePassword(oldPassword, newPassword) dengan validasi:
  • oldPassword harus benar
  • newPassword minimal 8 karakter
Tujuan akhirnya adalah membuat class dengan kontrol akses yang baik terhadap data sensitif.

Challenge #5: Shopping Cart dengan Observer Pattern

Pada tantangan terakhir, Anda diminta menerapkan Observer Pattern, yaitu sebuah pola desain yang memungkinkan sebuah objek (subject) memberi notifikasi kepada objek lain (observers) setiap terjadi perubahan.

Anda perlu membuat:
  1. ShoppingCart (Subject)
    • Memberikan notifikasi ketika:
    • Item ditambahkan
    • Item dihapus
    • Total price berubah
  2. Observers
    • EmailNotifier
    • LoggerNotifier
    • AnalyticsTracker
Setiap observer harus menerima notifikasi dan bertindak sesuai fungsinya. Tantangan ini akan membantu Anda memahami bagaimana aplikasi modern seperti e-commerce memantau aktivitas pengguna secara real-time.

Komentar

Postingan populer dari blog ini

SIPD Chrome Extension untuk Migrasi Data dari SIPD ke SIMDA PINK Th. Anggaran 2022

Mapping Sumber Dana di WP-SIPD

Dokumentasi Aplikasi Migrasi Aset SIMDA BMD