Gesaan dan skop
Pengendali memperoleh sumber segerak dan tak segerak yang jangka hayatnya mesti berakhir pada sempadan blok. Gunakan protokol pengurusan sumber eksplisit JavaScript untuk mereka bentuk pembalut yang selamat, kemudian terangkan perkara yang berlaku apabila pembinaan, pelaksanaan badan atau pembersihan gagal. Jawapan harus membezakan sintaks bahasa daripada sokongan masa jalan dan pustaka; TypeScript 5.2 boleh menyemak jenis sintaks, manakala ketersediaan pengeluaran masih bergantung pada enjin sasaran dan strategi transpilasi.
Ini ialah soalan coding kerana kemahiran terasnya ialah penaakulan jangka hayat sumber dan pelaksanaan selamat daripada kegagalan, bukan pemilihan rangka kerja.
Perkara yang dinilai oleh penemu duga
Pertama, bolehkah anda menentukan sumber dengan [Symbol.dispose]() untuk pembersihan segerak dan [Symbol.asyncDispose]() untuk pembersihan yang mesti ditunggu (awaited)?
Kedua, adakah anda faham bahawa using dan await using ialah pengisytiharan berskop? Pembersihan berjalan apabila kawalan meninggalkan blok yang mengandungi, termasuk pengecualian; skop peringkat atas jangka panjang biasanya merupakan jangka hayat yang salah.
Ketiga, bolehkah anda menyatakan susunan pelupusan? Sumber dilupuskan dalam susunan pengisytiharan terbalik, jadi sumber yang bergantung harus diisytiharkan selepas sumber yang mereka sandarkan.
Keempat, bolehkah anda menaakul tentang ralat? Ralat badan dan ralat pelupusan mungkin kedua-duanya perlu dikekalkan sebagai rantaian ralat yang disekat (suppressed error chain); pembersihan tidak boleh menggantikan kegagalan utama secara senyap.
Kelima, bolehkah anda menyediakan pelan keserasian? Pengesanan ciri, transformasi penyusun atau penyesuai try/finally eksplisit mungkin diperlukan apabila masa jalan pelaksanaan tidak melaksanakan protokol tersebut.
Soalan untuk dijelaskan terlebih dahulu
- Versi Node.js atau pelayar manakah yang melaksanakan kod tersebut, dan adakah transpilasi dibenarkan?
- Sumber manakah yang segerak, dan operasi pembersihan manakah yang mengembalikan janji (promise)?
- Adakah pembatalan menutup sumber serta-merta, atau adakah kerja dalam proses boleh diselesaikan?
- Adakah sumber bebas, atau adakah satu pembersihan bergantung pada sumber lain yang masih dibuka?
- Adakah ralat pembersihan mesti menggagalkan permintaan, dilaporkan atau dilampirkan pada ralat utama?
- Bolehkah kod menggunakan
DisposableStackatau simbol asas sahaja?
Rangka kerja jawapan 30 saat
"Saya akan memberikan setiap sumber protokol pelupusan eksplisit, mengisytiharkannya di dalam blok terkecil yang memiliki jangka hayatnya, dan menggunakan await using apabila pembersihan adalah tak segerak. Saya akan mengisytiharkan kebergantungan kemudian supaya pelupusan susunan terbalik selamat, menguji kejayaan, kegagalan badan, kegagalan perolehan dan pembatalan, serta mengekalkan kedua-dua ralat badan dan pembersihan. Sebelum menghantar ke persekitaran pengeluaran, saya akan mengesahkan sokongan enjin atau menyusun kepada try/finally yang setara; sokongan sintaks dalam TypeScript tidak menjamin sokongan masa jalan."
Jawapan langkah demi langkah
Langkah 1: Tentukan protokol pelupusan yang sempit
Kekalkan perolehan dan pembersihan bersama-sama. Sumber segerak mendedahkan [Symbol.dispose]() dan mesti menyelesaikan pembersihan sebelum blok keluar. Sumber tak segerak mendedahkan [Symbol.asyncDispose]() dan diperoleh dengan await using supaya laluan keluar yang dijana menunggunya (awaits).
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() }
}Kaedah harus bersifat idempoten jika pemanggil juga boleh membatalkan secara eksplisit. Jangan sekali-kali mengembalikan janji (promise) daripada [Symbol.dispose](); gunakan protokol async untuk kerja yang ditunggu.
Langkah 2: Pastikan blok jangka hayat kecil
Peroleh pajakan hanya selepas memasuki skop yang memilikinya. Elakkan menyimpan pemboleh ubah using dalam objek yang berhayat lebih lama; pembersihannya terikat pada blok leksikal, bukan pada kutipan sampah (garbage collection).
async function handle() {
{
using file = openFileLease()
await using lock = await acquireLockLease()
await writeWithLock(file, lock)
}
}Di sini kunci diisytiharkan selepas fail, jadi kunci dilepaskan terlebih dahulu dan fail ditutup kedua. Jika badan melontarkan ralat, kedua-dua laluan keluar tetap berjalan.
Langkah 3: Kendalikan perolehan dan pembatalan
Peroleh sumber secara berurutan atau daftarkannya dalam timbunan sebaik sahaja perolehan berjaya. Jika perolehan kemudian gagal, sumber yang telah diperoleh mesti tetap dilupuskan. Sambungkan isyarat henti paksa (abort signal) kepada operasi, tetapi kekalkan pelupusan dalam skop supaya pembatalan tidak dapat memintas pembersihan.
Langkah 4: Kekalkan maklumat kegagalan
Uji pengecualian badan dan pengecualian pembersihan secara berasingan, kemudian bersama-sama. Masa jalan boleh mewakili kegagalan pembersihan sebagai disekat oleh ralat utama; pengelogan harus merangkumi keseluruhan rantaian. Jika penyesuai menggunakan try/finally, lampirkan kegagalan pembersihan secara eksplisit dan bukannya menulis ganti ralat badan.
Langkah 5: Rancang keserasian
Semak enjin penggunaan sebenar, bukan hanya penyusun TypeScript. Apabila sintaks atau simbol asli tidak tersedia, susun kepada try/finally, gunakan polyfill yang disahkan, atau bungkus sumber dalam pembantu boleh lupus peringkat aplikasi. Pastikan semantik sandaran adalah sama: susunan terbalik, pembersihan sekali sahaja, pelepasan tak segerak yang ditunggu dan ralat yang dikekalkan.
Langkah 6: Uji sempadan kitaran hayat
Gunakan pemalsuan deterministik yang merekodkan peristiwa perolehan dan pelupusan. Liputi pemulangan normal, lontaran ralat badan, kegagalan perolehan kedua, henti paksa semasa kerja, kegagalan pelupusan dan pelupusan berulang. Pastikan susunan peristiwa dan tiada sumber yang kekal dibuka selepas janji selesai.
Contoh jawapan
"Saya memodelkan setiap pemegang sebagai pajakan boleh lupus. Pemegang segerak melaksanakan [Symbol.dispose]; pelepasan tak segerak melaksanakan [Symbol.asyncDispose]. Saya menciptanya di dalam blok pemilikan terkecil, menggunakan await using untuk kunci, dan mengisytiharkan kunci selepas fail supaya pembersihan susunan terbalik menghormati kebergantungan. Skop keluar semasa kembali, melontarkan ralat dan pembatalan, jadi pembersihan dijamin oleh protokol bahasa.
Saya menguji kegagalan perolehan, kegagalan badan, kegagalan pembersihan dan gabungannya, mengekalkan ralat utama serta maklumat pembersihan yang disekat. Saya juga mengesahkan sifat idempoten dan tingkah laku henti paksa. Akhir sekali, saya menyemak enjin pengeluaran: sokongan TypeScript 5.2 ialah bantuan masa penyusunan, bukan bukti bahawa masa jalan melaksanakan simbol tersebut. Jika sokongan tiada, saya mentranspilasi atau menggunakan penyesuai try/finally eksplisit dengan susunan dan semantik ralat yang sama."
Kesilapan biasa
- Meletakkan sumber dalam skop yang berhayat panjang → pembersihan tertangguh → ikatkannya pada blok pemilikan terkecil.
- Menggunakan
usinguntuk pembersihan async → janji mungkin diabaikan → laksanakan dan gunakan[Symbol.asyncDispose]denganawait using. - Mengisytiharkan kebergantungan dahulu → susunan terbalik menutupnya terlalu awal → isytiharkan pihak yang bergantung kemudian.
- Menganggap sokongan TypeScript bermakna sokongan masa jalan → pengeluaran gagal semasa penghuraian atau carian simbol → semak enjin atau susun sandaran.
- Menulis ganti ralat badan dengan kegagalan pembersihan → punca utama hilang → kekalkan ralat utama dan butiran pembersihan yang disekat.
- Membenarkan pelepasan berganda → pembersihan menjadi tidak selamat → jadikan pelupusan idempoten atau lindunginya.
- Hanya menguji kejayaan → laluan kegagalan membocorkan sumber → uji ralat perolehan, badan, pembatalan dan pelupusan.
Soalan susulan
Soalan susulan 1: Adakah using menggantikan kutipan sampah?
Tidak. Ia memberikan pembersihan berasaskan skop yang deterministik untuk sumber seperti pemegang dan kunci; penambakan memori kekal sebagai tugas masa jalan.
Soalan susulan 2: Mengapakah pelupusan disusun secara terbalik?
Pengisytiharan terkemudian lazimnya bergantung pada pengisytiharan terdahulu. Susunan terbalik membolehkan pihak yang bergantung dilepaskan sebelum kebergantungannya ditutup.
Soalan susulan 3: Bilakah pembersihan patut dijalankan secara tak segerak?
Gunakan protokol async apabila pelepasan itu sendiri memerlukan operasi yang ditunggu, seperti mengepam data keluar (flushing) atau mengembalikan pajakan kepada penyelaras jauh.
Soalan susulan 4: Bagaimana jika ralat badan dan ralat pembersihan berlaku serentak?
Kekalkan ralat badan sebagai ralat utama dan dedahkan kegagalan pembersihan melalui mekanisme ralat disekat masa jalan atau rantaian ralat eksplisit yang setara.
Soalan susulan 5: Bolehkah sumber dilupuskan secara manual juga?
Boleh, tetapi jadikan operasi itu idempoten atau selaraskan pemilikan supaya keluar dari skop tidak melepaskannya dua kali.
Soalan susulan 6: Apakah sandaran tanpa sokongan asli?
Gunakan transformasi penyusun, polyfill yang disahkan atau penyesuai try/finally kecil yang mengekalkan susunan terbalik, penungguan (awaiting), pembersihan sekali sahaja dan rantaian ralat.