Petunjuk dan cakupan
Handler memperoleh sumber daya sinkron dan asinkron yang masa pakainya (lifetime) harus berakhir pada batas blok. Gunakan protokol manajemen sumber daya eksplisit JavaScript untuk merancang wrapper yang aman, lalu jelaskan apa yang terjadi ketika konstruksi, eksekusi bodi, atau pembersihan gagal. Jawaban harus membedakan sintaks bahasa dari dukungan runtime dan pustaka; TypeScript 5.2 dapat memeriksa tipe sintaksisnya, sementara ketersediaan di lingkungan produksi tetap bergantung pada target engine dan strategi transpilasi.
Ini adalah pertanyaan coding karena keahlian intinya adalah penalaran masa pakai sumber daya dan implementasi yang aman dari kegagalan (failure-safe), bukan pemilihan framework.
Apa yang dinilai oleh pewawancara
Pertama, apakah Anda dapat mendefinisikan sumber daya dengan [Symbol.dispose]() untuk pembersihan sinkron dan [Symbol.asyncDispose]() untuk pembersihan yang harus di-await?
Kedua, apakah Anda memahami bahwa using dan await using adalah deklarasi dengan cakupan blok (scoped)? Pembersihan berjalan ketika kontrol keluar dari blok penampung, termasuk pengecualian (exception); cakupan tingkat atas (top-level) yang berumur panjang biasanya merupakan masa pakai yang salah.
Ketiga, apakah Anda dapat menyatakan urutan pelepasan (disposal)? Sumber daya dilepaskan dalam urutan terbalik dari deklarasinya, sehingga sumber daya yang bergantung harus dideklarasikan setelah sumber daya yang menjadi dependensinya.
Keempat, apakah Anda dapat menalar penanganan error? Error pada bodi dan error pada pelepasan mungkin keduanya perlu dipertahankan sebagai rantai error yang disupresi (suppressed error chain); pembersihan tidak boleh secara diam-diam menggantikan kegagalan utama.
Kelima, apakah Anda dapat memberikan rencana kompatibilitas? Deteksi fitur, transformasi compiler, atau adapter try/finally eksplisit mungkin diperlukan ketika runtime deployment tidak mengimplementasikan protokol tersebut.
Pertanyaan klarifikasi terlebih dahulu
- Versi Node.js atau browser apa yang mengeksekusi kode, dan apakah transpilasi diizinkan?
- Sumber daya mana yang sinkron, dan operasi pembersihan mana yang mengembalikan promise?
- Apakah pembatalan langsung menutup sumber daya, atau pekerjaan yang sedang berjalan diizinkan selesai?
- Apakah sumber daya saling independen, atau pembersihan satu sumber daya bergantung pada sumber daya lain yang masih terbuka?
- Apakah error pembersihan harus menggagalkan request, dilaporkan, atau dilampirkan pada error utama?
- Bisakah kode menggunakan
DisposableStackatau hanya simbol dasar?
Kerangka jawaban 30 detik
"Saya akan memberikan setiap sumber daya protokol pelepasan eksplisit, mendeklarasikannya di dalam blok terkecil yang memiliki masa pakainya, dan menggunakan await using setiap kali pembersihan bersifat asinkron. Saya akan mendeklarasikan dependensi belakangan agar pelepasan dengan urutan terbalik aman, menguji kasus sukses, kegagalan bodi, kegagalan akuisisi, dan pembatalan, serta mempertahankan error bodi maupun pembersihan. Sebelum merilis ke produksi, saya akan memverifikasi dukungan engine atau mengompilasinya ke try/finally yang setara; dukungan sintaks di TypeScript tidak menjamin dukungan runtime."
Jawaban langkah demi langkah
Langkah 1: Definisikan protokol pelepasan yang sempit
Pertahankan akuisisi dan pembersihan bersama-sama. Sumber daya sinkron mengekspos [Symbol.dispose]() dan harus menyelesaikan pembersihan sebelum blok keluar. Sumber daya asinkron mengekspos [Symbol.asyncDispose]() dan diakuisisi dengan await using sehingga jalur keluar yang dihasilkan meng-await-nya.
class FileLease {
constructor(private readonly fd: number) {}
[Symbol.dispose]() { closeFile(this.fd) }
}
class AsyncLockLease {
constructor(private readonly release: () => Promise<void>) {}
async [Symbol.asyncDispose]() { await this.release() }
}Metode-metode tersebut harus bersifat idempoten jika pemanggil juga dapat membatalkan secara eksplisit. Jangan pernah mengembalikan promise dari [Symbol.dispose](); gunakan protokol async untuk pekerjaan yang di-await.
Langkah 2: Buat blok masa pakai tetap kecil
Dapatkan lease hanya setelah memasuki scope yang memilikinya. Hindari menyimpan variabel using dalam objek yang berumur lebih panjang; pembersihannya terikat pada blok leksikal, bukan pada garbage collection.
async function handle() {
{
using file = openFileLease()
await using lock = await acquireLockLease()
await writeWithLock(file, lock)
}
}Di sini lock dideklarasikan setelah file, sehingga lock dilepaskan terlebih dahulu dan file ditutup kedua. Jika bodi melempar exception, kedua proses keluar tetap berjalan.
Langkah 3: Tangani akuisisi dan pembatalan
Dapatkan sumber daya secara berurutan atau daftarkan ke dalam stack segera setelah akuisisi berhasil. Jika akuisisi berikutnya gagal, sumber daya yang sudah diperoleh sebelumnya harus tetap dilepaskan. Hubungkan abort signal ke operasi, tetapi pertahankan pelepasan di dalam scope sehingga pembatalan tidak dapat melewati pembersihan.
Langkah 4: Pertahankan informasi kegagalan
Uji exception pada bodi dan exception pada pembersihan secara terpisah, lalu bersama-sama. Runtime dapat merepresentasikan kegagalan pembersihan sebagai disupresi oleh error utama; pencatatan log harus mencakup rantai lengkapnya. Jika sebuah adapter menggunakan try/finally, lampirkan kegagalan pembersihan secara eksplisit alih-alih menimpa error bodi.
Langkah 5: Rencanakan kompatibilitas
Periksa runtime engine deployment yang sebenarnya, bukan hanya compiler TypeScript. Jika sintaks native atau simbol tidak tersedia, kompilasi ke try/finally, gunakan polyfill yang teruji, atau bungkus sumber daya dalam helper disposable tingkat aplikasi. Pertahankan semantik fallback yang identik: urutan terbalik, pembersihan hanya sekali, pelepasan async yang di-await, dan penanganan error yang dipertahankan.
Langkah 6: Uji batasan siklus hidup
Gunakan fake deterministik yang mencatat peristiwa akuisisi dan pelepasan. Cakup kasus return normal, throw pada bodi, kegagalan akuisisi kedua, abort selama bekerja, kegagalan pelepasan, dan pelepasan berulang. Pastikan urutan peristiwa dan tidak ada sumber daya yang tetap terbuka setelah promise selesai (settle).
Contoh jawaban model
"Saya memodelkan setiap handle sebagai disposable lease. Handle sinkron mengimplementasikan [Symbol.dispose]; pelepasan asinkron mengimplementasikan [Symbol.asyncDispose]. Saya membuatnya di dalam blok pemilik terkecil, menggunakan await using untuk lock, dan mendeklarasikan lock setelah file sehingga pembersihan urutan terbalik menghormati dependensi. Scope keluar saat return, throw, dan pembatalan, sehingga pembersihan dijamin oleh protokol bahasa.
Saya menguji kegagalan akuisisi, kegagalan bodi, kegagalan pembersihan, dan kombinasinya, mempertahankan error utama ditambah informasi pembersihan yang disupresi. Saya juga memverifikasi sifat idempoten dan perilaku pembatalan (abort). Terakhir, saya memeriksa engine produksi: dukungan TypeScript 5.2 adalah bantuan waktu kompilasi, bukan bukti bahwa runtime mengimplementasikan simbol-simbol tersebut. Jika dukungan tidak ada, saya melakukan transpilasi atau menggunakan adapter try/finally eksplisit dengan semantik urutan dan error yang sama."
Kesalahan umum
- Menempatkan sumber daya dalam scope berumur panjang → pembersihan tertunda → ikat ke blok pemilik terkecil.
- Menggunakan
usinguntuk pembersihan async → sebuah promise mungkin diabaikan → implementasikan dan gunakan[Symbol.asyncDispose]denganawait using. - Mendeklarasikan dependensi terlebih dahulu → urutan terbalik menutupnya terlalu cepat → deklarasikan yang bergantung belakangan.
- Mengasumsikan dukungan TypeScript berarti dukungan runtime → produksi gagal pada saat parsing atau pencarian simbol → periksa engine atau kompilasi fallback.
- Menimpa error bodi dengan kegagalan pembersihan → akar penyebabnya hilang → pertahankan error utama dan detail pembersihan yang disupresi.
- Membiarkan pelepasan ganda → pembersihan menjadi tidak aman → buat pelepasan menjadi idempoten atau beri proteksi.
- Hanya menguji jalur sukses → jalur kegagalan membocorkan sumber daya → uji error pada akuisisi, bodi, pembatalan, dan pelepasan.
Pertanyaan lanjutan
Pertanyaan lanjutan 1: Apakah using menggantikan garbage collection?
Tidak. Ini menyediakan pembersihan berbasis scope yang deterministik untuk sumber daya seperti handle dan lock; reklamasi memori tetap menjadi tugas runtime.
Pertanyaan lanjutan 2: Mengapa pelepasan dilakukan dalam urutan terbalik?
Deklarasi yang lebih baru umumnya bergantung pada deklarasi sebelumnya. Urutan terbalik memungkinkan entitas yang bergantung dilepaskan sebelum dependensinya ditutup.
Pertanyaan lanjutan 3: Kapan pembersihan harus asinkron?
Gunakan protokol async ketika proses pelepasan itu sendiri memerlukan operasi yang di-await, seperti melakukan flush atau mengembalikan lease ke koordinator jarak jauh.
Pertanyaan lanjutan 4: Bagaimana jika error bodi dan error pembersihan terjadi bersamaan?
Pertahankan error bodi sebagai error utama dan tampilkan kegagalan pembersihan melalui mekanisme suppressed-error milik runtime atau rantai error eksplisit yang setara.
Pertanyaan lanjutan 5: Bisakah sumber daya dilepaskan secara manual juga?
Bisa, tetapi buat operasi tersebut idempoten atau koordinasikan kepemilikan sehingga saat keluar dari scope sumber daya tidak dilepaskan dua kali.
Pertanyaan lanjutan 6: Apa fallback jika tidak ada dukungan native?
Gunakan transformasi compiler, polyfill yang teruji, atau adapter try/finally kecil yang mempertahankan urutan terbalik, penantian await, pembersihan hanya sekali, dan perantaian error.