ReturnType, Parameters, dan Awaited
Ambil tipe return dan argumen fungsi pihak ketiga tanpa menulis ulang, plus membuka Promise dengan Awaited.
Kenapa Utility Ini Ada?
Di project nyata kamu sering memakai fungsi library atau modul lain yang tipe return-nya kompleks: object bertingkat, hasil query, response API. Menyalin bentuknya manual ke interface sendiri berarti setiap update library bisa membuat salinanmu basi tanpa ada yang memberitahu. ReturnType<T>, Parameters<T>, dan Awaited<T> mengambil tipe LANGSUNG dari fungsi sumbernya, jadi tipe turunanmu selalu sinkron dengan implementasi aslinya. Ubah return function-nya, semua yang memakai tipenya ikut berubah dan compiler menunjukkan semua tempat yang terdampak.
ReturnType: Tipe Return Tanpa Memanggil Fungsi
ReturnType<T> mengambil tipe dari nilai yang dikembalikan fungsi bertipe T. Karena ia butuh TIPE fungsi (bukan nilainya), pasangannya adalah typeof yang mengambil tipe dari sebuah nilai:
// Fungsi dari library atau modul lain, return-nya kompleks
function formatRupiah(nilai: number): { teks: string; mentah: number } {
return { teks: `Rp${nilai.toLocaleString("id-ID")}`, mentah: nilai };
}
type HasilFormat = ReturnType<typeof formatRupiah>;
// { teks: string; mentah: number }
function tampilkanHarga(n: number, fmt: (x: number) => HasilFormat): string {
return fmt(n).teks;
}
tampilkanHarga(50000, formatRupiah); // OK, signature cocok otomatisTanpa ReturnType, kamu menulis ulang bentuk return di setiap pemakaian. Dengan ReturnType<typeof formatRupiah>, fungsi itu sendiri adalah satu-satunya sumber kebenaran.
Parameters: Tipe Argumen sebagai Tuple
Parameters<T> mengambil tipe parameter fungsi sebagai tuple, sehingga kamu bisa meneruskannya, menyimpannya, atau melakukan spread dengan aman:
function buatPesanan(id: string, jumlah: number, prioritas?: boolean): void {
console.log(id, jumlah, prioritas);
}
type ArgumenPesanan = Parameters<typeof buatPesanan>;
// [id: string, jumlah: number, prioritas?: boolean | undefined]
const args: ArgumenPesanan = ["P-1", 3];
buatPesanan(...args); // OK, spread tuple tetap type-safe
// Pola wrapper generik: bungkus fungsi apa pun tanpa tahu signature-nya
function denganLog<T extends (...a: never[]) => unknown>(fn: T) {
return (...args: Parameters<T>): ReturnType<T> => {
console.log("dipanggil dengan:", args);
return fn(...args);
};
}
const pesananBerlog = denganLog(buatPesanan);
pesananBerlog("P-2", 5, true); // signature asli tetap terjagaPasangan Parameters<T> + ReturnType<T> adalah fondasi pola wrapper: tambah logging, caching, atau retry tanpa mengulang signature di mana pun.
Awaited: Membuka Promise
Fungsi async selalu mengembalikan Promise<...>, tapi yang kamu butuhkan biasanya adalah tipe di DALAM promise-nya. Awaited<T> membuka lapisan Promise secara rekursif:
async function ambilUser(id: string): Promise<{ id: string; nama: string }> {
return { id, nama: "Budi" };
}
type User = Awaited<ReturnType<typeof ambilUser>>;
// { id: string; nama: string } (bukan Promise<...>)
async function proses(id: string): Promise<void> {
const u: User = await ambilUser(id); // tipe cocok tanpa Promise
console.log(u.nama);
}
// Awaited membuka berlapis-lapis sekaligus
type Dalam = Awaited<Promise<Promise<number>>>; // numberIdiom Awaited<ReturnType<typeof fn>> memberi "tipe data sebenarnya" dari fungsi async milik orang lain dalam satu baris.
Contoh Error Nyata: Dipakai ke Bukan Fungsi
Ketiga utility ini mensyaratkan tipe fungsi. Memakainya ke tipe lain menghasilkan error constraint yang cukup jelas:
const nama = "Budi";
type Salah1 = ReturnType<typeof nama>;
// Error: Type 'string' does not satisfy the constraint '(...args: any) => any'.
type Salah2 = Parameters<number>;
// Error: Type 'number' does not satisfy the constraint '(...args: any) => any'.Pesan does not satisfy the constraint memberitahu syarat yang gagal: argumen harus bertipe fungsi. Penyebab paling sering adalah lupa typeof: ReturnType menerima tipe, sedangkan formatRupiah tanpa typeof adalah nilai. Aturan praktis: bungkus nilai fungsi dengan typeof dulu sebelum masuk ke ReturnType atau Parameters.
Jebakan kedua: pada fungsi overload, ReturnType<typeof fn> hanya mengambil signature overload TERAKHIR. Kalau wrapper-mu aneh pada fungsi overload, itu penyebabnya.
Catatan teknis:
ReturnType<F>didefinisikan kira-kira sebagaiF extends (...args: never[]) => infer R ? R : never: kata kunciinfer"menangkap" tipe return dari signature. Ketiga utility ini dibangun di atas conditional type +infer, jembatan alami menuju modul conditional types.
Tantangan
Wrapper async yang type-safe
Diberi declare function hitungDiskon(harga: number, persen: number): Promise<number>. Definisikan TipeHasil = Awaited<ReturnType<typeof hitungDiskon>> dan Argumen = Parameters<typeof hitungDiskon>. Lalu tulis fungsi async terapkanDiskon(...args: Argumen): Promise<TipeHasil> yang memanggil hitungDiskon dan mengembalikan hasilnya. Buktikan tipe cocok dengan satu pemanggilan contoh.