narrowingininstanceofMenengah3 mnt baca

Narrowing dengan in dan instanceof

Persempit union object dengan operator in dan instanceof untuk class.

Kenapa typeof saja tidak cukup

typeof cuma bisa membedakan delapan kategori primitif. Begitu union berisi dua object, typeof menyerah: keduanya "object". Di sinilah operator in (cek keberadaan properti) dan instanceof (cek rantai prototype class) masuk.

Alur narrowing dengan in, langkah demi langkah

Skenario nyata: response API yang bisa sukses atau gagal, tapi tanpa field penanda yang rapi (misalnya API warisan yang tidak kamu desain).

typescript
interface ResponSukses { data: string[]; total: number }
interface ResponGagal { pesanError: string; kode: number }

type ResponApi = ResponSukses | ResponGagal;

function tampilkan(r: ResponApi): string {
  // Langkah 1: cek properti yang cuma ada di satu varian.
  // Di dalam if, r: ResponSukses.
  if ("data" in r) {
    return `Ketemu ${r.total} item: ${r.data.join(", ")}`;
  }
  // Langkah 2: yang tersisa pasti ResponGagal.
  return `Gagal [${r.kode}]: ${r.pesanError}`;
}

Compiler tahu data cuma milik ResponSukses, jadi cabang else otomatis jadi ResponGagal. Elegan, tapi rapuh: kalau suatu hari ResponGagal juga punya properti data, narrowing-nya jebol diam-diam. Makanya untuk union yang kamu desain sendiri, discriminated union (modul berikutnya) lebih aman karena tidak bergantung pada properti yang kebetulan unik.

Alur narrowing dengan instanceof

instanceof dipakai untuk instance class, paling sering untuk error:

typescript
class ApiError extends Error {
  constructor(public kode: number) {
    super("API error");
  }
}

async function panggilApi(): Promise<void> {
  try {
    await fetch("/api/data");
  } catch (e: unknown) {
    // Langkah 1: e masih unknown, tidak bisa diapa-apakan.
    // Langkah 2: instanceof mempersempit ke ApiError.
    if (e instanceof ApiError) {
      console.log(`API ngambek, kode ${e.kode}`); // e.kode aman
    } else if (e instanceof Error) {
      console.log(e.message); // Error biasa
    } else {
      console.log("bukan error sama sekali:", e);
    }
  }
}

Urutan penting: cek subclass (ApiError) dulu sebelum parent (Error), karena instanceof Error juga true untuk ApiError. Kebalik urutannya, cabang ApiError tidak akan pernah tercapai.

Error compiler nyata

Mengakses properti spesifik tanpa narrowing:

typescript
function tangani(e: Error | ApiError) {
  console.log(e.kode);
}
text
error TS2339: Property 'kode' does not exist on type 'Error | ApiError'.
  Property 'kode' does not exist on type 'Error'.

Fix-nya: if (e instanceof ApiError) dulu, baru akses e.kode di dalamnya.

Kapan dipakai di project nyata

  • Operator in: parsing webhook atau event dari pihak ketiga yang bentuknya beda-beda tapi tidak punya field penanda. Juga buat membedakan opsi konfigurasi opsional yang tumpang tindih.
  • instanceof: error handling berlapis (bedakan ApiError, ValidationError, Error biasa lalu mapping ke status HTTP yang beda), cek x instanceof Date sebelum format tanggal, atau bedakan File vs string di fungsi upload.

Catatan teknis: instanceof bisa berbohong kalau objek datang dari realm lain (misalnya iframe atau VM Node yang berbeda): rantai prototype-nya beda, jadi [] instanceof Array bisa false. Untuk kasus lintas-realm, pakai cek yang lebih portabel seperti Array.isArray().

Tantangan

Dispatcher notifikasi

Buat interface Email { alamat: string; subjek: string } dan interface SMS { nomor: string; pesan: string }. Fungsi kirim(n: Email | SMS): string memakai in untuk membedakan: kembalikan "Email ke ..." atau "SMS ke ...".

typescript
interface Email { alamat: string; subjek: string; }
interface SMS { nomor: string; pesan: string; }

function kirim(n: Email | SMS): string {
  // pakai "alamat" in n
}

console.log(kirim({ alamat: "[email protected]", subjek: "Hai" }));
console.log(kirim({ nomor: "0812", pesan: "Halo" }));