คีย์เวิร์ด using ใน TypeScript และการจัดการทรัพยากรแบบชัดเจน (Explicit Resource Management)
ปัญหาหน่วยความจำรั่ว (Memory leaks) ในระบบที่ใช้งานจริง (production) มักเกิดจากข้อผิดพลาดเพียงอย่างเดียว นั่นคือ นักพัฒนาทำการจองทรัพยากร (acquire resources) แต่ลืมคืนทรัพยากรเหล่านั้น (release them)
การเชื่อมต่อฐานข้อมูลยังคงเปิดค้างไว้หลังจากเกิดข้อผิดพลาด, File handles ใช้ทรัพยากรระบบไปโดยเปล่าประโยชน์, หรือ WebSocket clients ยังคงเชื่อมต่อกับเซิร์ฟเวอร์ที่ตายไปแล้ว สิ่งเหล่านี้เกิดขึ้นเมื่อคุณพึ่งพาการทำความสะอาดข้อมูลด้วยตนเอง (manual cleanup) ภายในบล็อก finally
คีย์เวิร์ด using ใน TypeScript ช่วยแก้ปัญหานี้ ซึ่งเป็นส่วนหนึ่งของข้อเสนอ Explicit Resource Management ของ ECMAScript โดยมันจะรับประกันการทำความสะอาดผ่านรูปแบบ disposable pattern เมื่อทรัพยากรหลุดออกจากขอบเขต (scope) ของมัน TypeScript จะเรียกใช้เมธอดการทำลาย (disposal method) โดยอัตโนมัติ
คุณไม่จำเป็นต้องเขียนบล็อก finally ด้วยตนเองอีกต่อไป ไม่ต้องกังวลเรื่องการลืมทำความสะอาดข้อมูล และไม่ต้องเจอปัญหาการเชื่อมต่อรั่วไหลอีกต่อไป
หลักการทำงาน:
- คีย์เวิร์ด
usingช่วยรับประกันการทำลายทรัพยากรเมื่อทรัพยากรนั้นหลุดออกจาก scope - ทรัพยากรที่สามารถทำลายได้ (Disposable resources) จะต้อง implement
Symbol.disposeสำหรับงานแบบ synchronous หรือSymbol.asyncDisposeสำหรับงานแบบ asynchronous - TypeScript จะเปลี่ยนการประกาศ
usingให้กลายเป็นบล็อกtry-finallyพร้อมกับมี stack สำหรับการทำลายทรัพยากรแบบอัตโนมัติ - รูปแบบนี้ช่วยป้องกันการรั่วไหลที่เกิดจากการ return ก่อนกำหนด (early returns), การโยน exception, หรือการลืมเขียนโค้ดจัดการ
ควรใช้ using กับทรัพยากรใดก็ตามที่มีอายุการใช้งานที่แน่นอน เช่น การเชื่อมต่อฐานข้อมูล, file handles, locks หรือ timers
กลไกนี้ใช้โปรโตคอลการทำลาย (disposal protocol) โดยออบเจกต์จะต้อง implement เมธอดที่ระบุด้วย Symbol.dispose เมื่อ scope สิ้นสุดลงไม่ว่าจะด้วยการทำงานเสร็จสิ้นตามปกติ, การ return, หรือการเกิด exception ตัว TypeScript จะเรียกใช้เมธอดนั้นทันที
ตัวอย่างการใช้งานกับ file handle:
class FileHandle {
private handle: number;
constructor(path: string) {
this.handle = openFileSync(path);
}
[Symbol.dispose]() {
if (this.handle !== -1) {
closeFileSync(this.handle);
this.handle = -1;
}
}
read(buffer: Buffer): number {
return readSync(this.handle, buffer);
}
}
function processFile(path: string) {
using file = new FileHandle(path);
const buffer = Buffer.alloc(1024);
file.read(buffer);
}
การทำลายทรัพยากรจะทำงานภายในบล็อก finally ที่ถูกสร้างขึ้นมา ซึ่งจะทำงานเสมอแม้ว่าฟังก์ชันจะโยน error หรือมีการ return ออกมาก่อนกำหนดก็ตาม
TypeScript จะรักษา disposal stack ไว้ โดยจะทำลายทรัพยากรในลำดับย้อนกลับจากตอนที่คุณจองทรัพยากรมา ซึ่งสอดคล้องกับลำดับความสัมพันธ์ตามธรรมชาติของตรรกะการทำความสะอาดข้อมูลส่วนใหญ่
สำหรับการทำความสะอาดแบบ asynchronous ให้ใช้ await using ซึ่งกำหนดให้ทรัพยากรนั้นต้อง implement Symbol.asyncDispose โดย TypeScript จะรอ (await) Promise ที่ส่งกลับมาให้เสร็จสิ้นก่อนที่จะทำงานต่อไป
กฎ 3 ข้อเพื่อความสำเร็จ:
- อย่าโยน exception ภายในเมธอดการทำลายทรัพยากร (disposal methods) ของคุณ แต่ให้ใช้วิธี catch และ log error ภายในแทน
- อย่าใช้การทำงานแบบ async ภายในเมธอด
Symbol.disposeที่เป็น synchronous ให้ใช้Symbol.asyncDisposeแทน - จำไว้ว่าการประกาศ
usingมีขอบเขตเป็นแบบ block-scoped ทรัพยากรที่อยู่ในบล็อกifจะถูกทำลายเมื่อบล็อกนั้นสิ้นสุดลง ไม่ใช่เมื่อฟังก์ชันสิ้นสุดลง
รูปแบบนี้ช่วยเพิ่มความปลอดภัยโดยมีต้นทุนในการทำงาน (runtime cost) ที่น้อยมากจนแทบไม่มีผล
Source: https://dev.to/jsmanifest/typescript-using-keyword-and-explicit-resource-management-done-right-22pg
