decoratorsexperimentalmetadataMahir5 mnt baca

Decorator Factory

Memahami decorator factory: decorator berparameter seperti @Log dan @Measure, cara membuatnya langkah demi langkah, dan cara memperbaiki error TS1248.

Decorator Factory

Decorator adalah fitur eksperimental TypeScript yang memungkinkan kamu menempelkan perilaku tambahan ke class, method, accessor, atau properti hanya dengan menulis anotasi seperti @sealed di atasnya. Decorator biasa tidak menerima argumen, perilakunya sudah tetap sejak ditulis. Decorator factory adalah fungsi yang mengembalikan decorator, sehingga decorator yang dihasilkan bisa menerima parameter, contohnya @Log("API") atau @Measure("ms").

Kenapa factory dibutuhkan? Bayangkan kamu ingin mencatat log setiap method penting, tetapi tiap modul butuh prefix berbeda, atau kamu ingin mengukur durasi eksekusi dengan label berbeda per method. Tanpa factory, kamu harus menulis decorator terpisah untuk tiap varian, dan kode cepat menjadi duplikat. Dengan factory, satu definisi fungsi melayani semua konfigurasi: parameter yang kamu teruskan saat memakai decorator menentukan perilakunya.

Membuat decorator factory langkah demi langkah

Kita buat @Log(prefix) yang mencetak nama method beserta argumennya setiap kali method dipanggil:

typescript
function Log(prefix: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
      console.log(`[${prefix}] ${propertyKey}`, args);
      return original.apply(this, args);
    };
  };
}

class Kalkulator {
  @Log("hitung")
  tambah(a: number, b: number) {
    return a + b;
  }
}

new Kalkulator().tambah(2, 3);
// output: [hitung] tambah [2, 3]

Perhatikan alur eksekusinya dalam tiga tahap. Pertama, Log("hitung") dipanggil saat definisi class dibaca, dan nilai kembaliannya adalah fungsi decorator yang sesungguhnya. Kedua, TypeScript memanggil fungsi decorator itu dengan tiga argumen: target (prototype class), propertyKey (nama method, yaitu "tambah"), dan descriptor (deskriptor properti method). Ketiga, di dalam decorator kita membungkus method asli: setiap pemanggilan mencetak log lebih dulu, lalu meneruskan ke method asli dengan original.apply(this, args) agar this tetap benar.

Contoh kedua, @Measure(label) yang mengukur berapa lama sebuah method berjalan:

typescript
function Measure(label: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const start = performance.now();
      const result = original.apply(this, args);
      const ms = (performance.now() - start).toFixed(2);
      console.log(`${label}: ${ms} ms`);
      return result;
    };
  };
}

class Layanan {
  @Measure("ambil-data")
  ambilData() {
    // simulasi kerja berat
    return [1, 2, 3];
  }
}

Pola ini sangat berguna untuk concern lintas fungsi seperti logging, timing, caching, atau validasi, karena logika tambahannya ditulis sekali lalu dipakai ulang di banyak method hanya dengan satu baris anotasi.

Error nyata: TS1248

Decorator masih berstatus eksperimental, jadi compiler menolaknya secara bawaan. Kalau kamu menulis decorator tanpa mengaktifkannya di tsconfig.json, kamu akan mendapat error yang membingungkan:

error TS1248: A decorator can only decorate a method implementation. This is an error when the 'experimentalDecorators' compiler flag is not set.

Catatan teknis: pesan TS1248 terdengar seperti masalah pada method yang kamu dekorasi, padahal akar masalahnya murni flag compiler yang belum aktif. Jangan buang waktu mengubah methodnya.

Cara memperbaiki: aktifkan flag experimentalDecorators di tsconfig.json:

json
{
  "compilerOptions": {
    "target": "ES2022",
    "experimentalDecorators": true
  }
}

Setelah flag aktif, decorator factory berjalan normal. Satu hal penting untuk diingat: decorator dievaluasi satu kali saat class didefinisikan, bukan setiap kali method dipanggil. Jadi jangan menaruh logika yang bergantung pada state instance di dalam factory-nya, karena factory hanya berjalan sekali di awal. Logika per pemanggilan harus tinggal di dalam fungsi pembungkus descriptor.value, seperti contoh Log dan Measure di atas.

Tantangan

Buat decorator factory @Retry

Buat decorator factory Retry(kesempatan: number) yang membungkus method: jika method melempar error, coba lagi hingga berhasil atau hingga jumlah kesempatan habis, lalu lempar error terakhir. Terapkan pada method simpan() di sebuah class dengan @Retry(3), dan pastikan experimentalDecorators aktif di tsconfig agar tidak kena TS1248.