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).
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:
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:
function tangani(e: Error | ApiError) {
console.log(e.kode);
}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 (bedakanApiError,ValidationError,Errorbiasa lalu mapping ke status HTTP yang beda), cekx instanceof Datesebelum format tanggal, atau bedakanFilevsstringdi fungsi upload.
Catatan teknis:
instanceofbisa berbohong kalau objek datang dari realm lain (misalnya iframe atau VM Node yang berbeda): rantai prototype-nya beda, jadi[] instanceof Arraybisa false. Untuk kasus lintas-realm, pakai cek yang lebih portabel sepertiArray.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 ...".
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" }));