題目與範圍
處理器會取得同步與非同步資源,生命週期必須在程式區塊邊界結束。請使用 JavaScript 顯式資源管理協定設計安全封裝,並說明建構、正文執行或清理失敗時的行為。回答要區分語法與執行環境支援;TypeScript 5.2 可以檢查相關語法,但正式環境可用性仍取決於目標引擎與轉譯方案。
核心能力是資源生命週期與失敗安全實作,因此歸入 coding。
面試官考察什麼
第一,能否用 [Symbol.dispose]() 表示同步清理,用 [Symbol.asyncDispose]() 表示必須等待的非同步清理?
第二,是否理解 using 與 await using 是有作用域的宣告?控制流程離開區塊時會清理,包括擲出例外;長期存在的頂層作用域通常不合適。
第三,能否說明釋放順序?資源按宣告的逆序釋放,因此有依賴關係時應先宣告依賴,再宣告依賴它的資源。
第四,能否處理錯誤?正文錯誤與清理錯誤可能同時發生,不能讓清理錯誤悄悄覆蓋主要錯誤。
第五,能否提供相容方案?目標執行環境不支援協定時,需要特性偵測、編譯轉換或等價的 try/finally 配接器。
先釐清的問題
- 正式環境使用哪些 Node.js 或瀏覽器版本,是否允許轉譯?
- 哪些資源是同步清理,哪些清理函式會回傳 Promise?
- 取消時必須立即關閉,還是允許目前操作完成?
- 資源之間是否有依賴,後釋放的資源是否依賴先釋放者?
- 清理錯誤應使請求失敗、記錄日誌,或附加到主要錯誤?
- 可以使用 DisposableStack,還是只能實作基礎符號?
30 秒回答框架
「我會為每個資源實作明確釋放協定,把宣告放在擁有它的最小程式區塊中;非同步清理使用 await using。依賴資源後宣告,利用逆序釋放保證順序安全;測試成功、正文例外、取得失敗與取消,並保留正文與清理錯誤。發布前驗證執行環境支援,或轉譯成等價的 try/finally;TypeScript 的語法支援不等於執行環境已經實作。」
分步作答
第一步:定義窄職責釋放協定
把取得與清理放在同一資源封裝中。同步資源實作 [Symbol.dispose]();非同步資源實作 [Symbol.asyncDispose](),並使用 await using 讓離開路徑等待清理完成。
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() }
}若呼叫者也可能主動取消,釋放函式應具備冪等性。[Symbol.dispose]() 不應回傳 Promise;需要等待時使用非同步協定。
第二步:縮小生命週期程式區塊
進入擁有資源的程式區塊後再取得資源,避免把 using 變數存進更長壽命的物件;它繫結的是詞法作用域,不是垃圾回收時間。
async function handle() {
{
using file = openFileLease()
await using lock = await acquireLockLease()
await writeWithLock(file, lock)
}
}鎖在檔案之後宣告,因此先釋放鎖,再關閉檔案。正文擲出例外時,兩條離開路徑仍會執行。
第三步:處理取得失敗與取消
資源一旦取得成功就要納入清理範圍。後續取得失敗時,已取得資源仍必須釋放。把取消訊號連接到業務操作,但不要讓取消繞過作用域清理。
第四步:保留完整失敗資訊
分別測試正文例外與清理例外,再測試兩者同時發生。日誌應包含主要錯誤與被抑制的清理錯誤;手寫 try/finally 配接器時也不能覆蓋正文錯誤。
第五步:規劃相容性
檢查實際部署引擎,而不是只看 TypeScript 編譯器。缺少原生語法或符號時,可使用編譯轉換、經過審核的 polyfill,或應用層 try/finally 配接器,並保持逆序、冪等、等待與錯誤鏈語意一致。
第六步:測試生命週期邊界
用可控假資源記錄取得與釋放事件,涵蓋正常回傳、正文擲錯、第二次取得失敗、工作中止、釋放失敗與重複釋放。斷言事件順序,並確認 Promise 結束後沒有資源遺留。
示例回答
「我把每個句柄建模為可釋放租約:同步句柄實作 [Symbol.dispose],非同步釋放實作 [Symbol.asyncDispose]。資源放在最小擁有程式區塊中,鎖使用 await using,並在檔案之後宣告,這樣逆序釋放時先解鎖再關檔。回傳、例外與取消都會離開作用域並觸發清理。
我會測試取得失敗、正文失敗、清理失敗及組合情況,保留主要錯誤與被抑制資訊,並驗證冪等與取消行為。最後檢查正式執行環境;TypeScript 5.2 的支援只是編譯期能力。若執行環境不支援,就轉譯或使用語意相同的 try/finally 配接器。」
常見錯誤
- 把資源放在長期作用域 → 清理延遲 → 繫結到最小擁有程式區塊。
- 用
using處理非同步清理 → Promise 可能未等待 → 使用非同步協定與await using。 - 先宣告依賴資源 → 逆序釋放過早關閉依賴 → 依賴先宣告,使用者後宣告。
- 把編譯器支援當成執行環境支援 → 線上解析或符號查找失敗 → 檢查引擎並準備降級。
- 用清理錯誤覆蓋正文錯誤 → 根因遺失 → 保留主要錯誤與清理錯誤鏈。
- 允許重複釋放 → 資源狀態損壞 → 保證冪等或集中所有權。
- 只測成功路徑 → 失敗路徑洩漏 → 涵蓋取得、正文、取消與清理錯誤。
追問
追問 1:using 會取代垃圾回收嗎?
不會。它為句柄、鎖等資源提供確定性的作用域清理;記憶體回收仍由執行環境負責。
追問 2:為什麼逆序釋放?
後宣告的資源通常依賴先宣告的資源,逆序能先釋放使用者,再關閉依賴。
追問 3:什麼時候需要非同步清理?
釋放本身需要等待時,例如刷新緩衝區或向遠端協調器歸還租約,就應使用非同步協定。
追問 4:正文與清理都失敗怎麼辦?
保留正文錯誤為主要錯誤,並透過執行環境抑制錯誤機制或明確錯誤鏈暴露清理失敗。
追問 5:可以手動釋放嗎?
可以,但要讓釋放冪等,或明確所有權,防止作用域退出時二次釋放。
追問 6:沒有原生支援如何降級?
使用編譯轉換、經過審核的 polyfill 或 try/finally 配接器,並保持逆序、等待、冪等與錯誤鏈語意。