7 คะแนน โดย GN⁺ 2025-05-18 | 4 ความคิดเห็น | แชร์ทาง WhatsApp
  • ข้อเสนอ Explicit Resource Management เป็นแนวทางใหม่ในการควบคุมวงจรชีวิตของทรัพยากรอย่าง ไฟล์แฮนเดิล และ การเชื่อมต่อเครือข่าย ได้อย่างชัดเจน
  • สามารถใช้งานฟีเจอร์นี้ได้ตั้งแต่ Chromium 134 และ V8 v13.8
  • สิ่งที่ถูกเพิ่มเข้ามาในภาษา
    • มีการเพิ่มสัญลักษณ์ using และ await using รวมถึง Symbol.dispose, Symbol.asyncDispose เพื่อให้มีกลไกการทำความสะอาดอัตโนมัติ
    • DisposableStack, AsyncDisposableStack ช่วยจัดกลุ่มและปล่อยหลายทรัพยากรได้อย่างปลอดภัย
    • SuppressedError ใช้จัดการทั้งข้อผิดพลาดที่เกิดขึ้นระหว่างการทำความสะอาดและข้อผิดพลาดเดิมร่วมกัน
  • วิธีนี้ช่วยเพิ่มความปลอดภัยของโค้ดและความสามารถในการดูแลรักษาได้อย่างมาก และมีประสิทธิภาพในการป้องกัน resource leak
  • ทำให้แพตเทิร์น try...finally แบบเดิมง่ายขึ้น และช่วยให้จัดการทรัพยากรได้อย่างน่าเชื่อถือในสภาพแวดล้อมที่มีทรัพยากรซับซ้อนขนาดใหญ่

ภาพรวมของข้อเสนอการจัดการทรัพยากรแบบชัดเจน

  • ข้อเสนอ Explicit Resource Management เพิ่มวิธีใหม่ที่ช่วยให้สามารถสร้างและปล่อยทรัพยากรอย่าง ไฟล์แฮนเดิล และ การเชื่อมต่อเครือข่าย ได้อย่างชัดเจน
  • องค์ประกอบหลักมีดังนี้
    • การประกาศ using และ await using: ปล่อยทรัพยากรอัตโนมัติเมื่อออกจากสโคป
    • สัญลักษณ์ [Symbol.dispose](), [Symbol.asyncDispose]() : เมธอดสำหรับนิยามการทำงาน cleanup
    • อ็อบเจ็กต์โกลบอล DisposableStack, AsyncDisposableStack: จัดกลุ่มหลายทรัพยากรเพื่อบริหารจัดการได้อย่างมีประสิทธิภาพ
    • SuppressedError: ประเภทข้อผิดพลาดแบบใหม่ที่รวมทั้งข้อผิดพลาดเดิมและข้อผิดพลาดที่เกิดระหว่างการล้างทรัพยากร
  • ฟีเจอร์เหล่านี้มุ่งเน้นให้ผู้พัฒนาสามารถ จัดการทรัพยากรได้ละเอียดขึ้น และช่วยยกระดับประสิทธิภาพกับความปลอดภัยของโค้ด

การประกาศ using และ await using

  • using ใช้กับทรัพยากรแบบซิงโครนัส ส่วน await using ใช้กับ ทรัพยากรแบบอะซิงโครนัส
  • เมื่อทรัพยากรที่ประกาศไว้หลุดออกจากสโคป ระบบจะเรียก Symbol.dispose หรือ Symbol.asyncDispose โดยอัตโนมัติ
  • แนวทางนี้ช่วยลดปัญหา resource leak ทั้งแบบซิงก์และอะซิงก์ และทำให้เขียนโค้ดสำหรับการปล่อยทรัพยากรได้อย่างสม่ำเสมอ
  • คีย์เวิร์ดนี้ใช้ได้เฉพาะภายใน code block, for loop และ function body เท่านั้น ไม่สามารถใช้ใน top-level ได้
  • ตัวอย่าง
    • เช่น เมื่อใช้ ReadableStreamDefaultReader จำเป็นต้องเรียก reader.releaseLock() เพื่อให้สามารถนำสตรีมกลับมาใช้ซ้ำได้
    • หากเกิดข้อผิดพลาดแล้วไม่ได้เรียกคำสั่งนี้ สตรีมอาจถูกล็อกถาวรได้
  • วิธีดั้งเดิม
    • ผู้พัฒนาจะใช้ บล็อก try...finally เพื่อรับประกันว่าการปลดล็อกของ reader จะเกิดขึ้นแน่นอน
    • ต้องเขียน reader.releaseLock() ไว้ในบล็อก finally
  • วิธีที่ปรับปรุงแล้ว: ใช้ using
    • สร้าง อ็อบเจ็กต์แบบ disposable (readerResource) ที่มีพฤติกรรมการปล่อยทรัพยากร
    • หากใช้แพตเทิร์น using readerResource = {...} ระบบจะปล่อยทรัพยากรโดยอัตโนมัติทันทีที่ออกจาก code block
    • ในอนาคต หาก Web API รองรับ [Symbol.dispose] และ [Symbol.asyncDispose] ก็อาจจัดการอัตโนมัติได้โดยไม่ต้องเขียน wrapper object แยกต่างหาก

DisposableStack และ AsyncDisposableStack

  • มีการเพิ่ม DisposableStack และ AsyncDisposableStack เพื่อ จัดกลุ่มหลายทรัพยากรได้อย่างมีประสิทธิภาพและปลอดภัย
  • สามารถเพิ่มทรัพยากรเข้าไปในแต่ละสแตก และเมื่อปล่อยสแตก ตัวทรัพยากรทั้งหมดภายในจะถูกปล่อยย้อนลำดับ
  • ช่วยลดความเสี่ยงและทำให้โค้ดง่ายขึ้นเมื่อต้องจัดการชุดทรัพยากรซับซ้อนที่มีความสัมพันธ์พึ่งพากัน
  • เมธอดหลัก
    • use(value): เพิ่มทรัพยากรแบบ disposable ไว้บนสุดของสแตก
    • adopt(value, onDispose): เพิ่มทรัพยากรที่ไม่ใช่ disposable พร้อมผูก callback สำหรับปล่อยทรัพยากร
    • defer(onDispose): เพิ่มเฉพาะการทำงานปล่อยทรัพยากรโดยไม่ต้องมี resource
    • move(): ย้ายทรัพยากรทั้งหมดจากสแตกปัจจุบันไปยังสแตกใหม่เพื่อโอนความเป็นเจ้าของ
    • dispose(), asyncDispose(): ปล่อยทรัพยากรทั้งหมดในสแตก

สถานะการรองรับและช่วงเวลาที่นำไปใช้งานได้

  • สามารถใช้งานการจัดการทรัพยากรแบบชัดเจนได้ตั้งแต่ Chromium 134, V8 v13.8 ขึ้นไป
  • คาดว่าในอนาคตจะรองรับร่วมกับ Web API ที่หลากหลายมากขึ้น

4 ความคิดเห็น

 
cichol 2025-05-18

await using data = await fn()
ปาฏิหาริย์ที่ await ปรากฏอยู่ทั้งฝั่งซ้ายและฝั่งขวา

 
tested 2025-05-18

พลังพิเศษใหม่ของ JavaScript: การจัดการทรัพยากรแบบชัดเจน

https://typescriptlang.org/docs/handbook/…

 
GN⁺ 2025-05-18
ความเห็นบน Hacker News
  • ข้อเสนอนี้ให้ความรู้สึกคล้ายปัญหา "สีของฟังก์ชัน" การแยกฟังก์ชันซิงก์กับอะซิงก์ยังคงแทรกซึมเข้าไปในทุกฟีเจอร์ ตัวอย่างเช่น Symbol.dispose กับ Symbol.asyncDispose และ DisposableStack กับ AsyncDisposableStack ดีใจที่ Java เลือกไปทาง virtual threads มองว่าเป็นการเพิ่มความซับซ้อนให้ JVM เพื่อช่วยลดภาระของนักพัฒนาแอป ผู้เขียนไลบรารี และดีบักเกอร์

    • ไม่เห็นด้วย เพราะการซ่อนความเป็นอะซิงก์ทำให้เข้าใจกระแสการทำงานของโค้ดยากขึ้น อยากรู้ด้วยว่าทรัพยากรถูกปล่อยแบบอะซิงก์หรือไม่ และอาจได้รับผลกระทบจากปัจจัยภายนอกอย่างปัญหาเครือข่ายหรือเปล่า

    • ทุกวันนี้หลายภาษาเหมือนมีค่านิยมว่า "โค้ดทุกอย่างควรเขียนแบบอะซิงก์" ซึ่งน่าหงุดหงิดมาก มองว่า Purescript เป็นกรณีแทบจะเดียวที่เขียนโค้ดด้วย Eff (เอฟเฟกต์แบบซิงก์) หรือ Aff (เอฟเฟกต์แบบอะซิงก์) แล้วค่อยเลือกตอนเรียกใช้ได้ Structured concurrency ฟังดูดี แต่ในทางปฏิบัติมักไม่ใช่งานเชิงไวยากรณ์เพื่อให้ได้ structured concurrency เท่าไรนัก กลับใกล้เคียงกับการเตรียมงานเพื่อให้เซิร์ฟเวอร์มี top-level request handler หลายตัวมากกว่า เป็นเพียงวิธีทำให้การประมวลผลขนานทำได้ง่ายขึ้น

    • ไม่รู้ว่า JVM ทำอย่างไร แต่โดยทั่วไปแล้วมัลติเธรดเป็นเทคโนโลยีที่รับมือให้เป็นธรรมชาติได้ยากมาก มีทั้ง race condition, deadlock, livelock, starvation, ปัญหา memory visibility ฯลฯ จนมีหนังสือพูดถึงมากมาย เมื่อเทียบกันแล้วการเขียนโปรแกรมอะซิงก์แบบเธรดเดียวนั้นภาระน้อยกว่ามาก ยอมรับปัญหาเรื่องสีของฟังก์ชันยังเจ็บปวดน้อยกว่าการไล่ดีบัก "Heisenbug" ในแอปมัลติเธรด

    • ดีใจจริง ๆ ที่ Java ตัดสินใจแบบนั้น

    • มีคำอธิบายว่าเป็นเพราะการทำงานปกติกับฟังก์ชันอะซิงก์ต่างก็สร้าง closed Cartesian categories ของตัวเอง หมวดของการทำงานปกติสามารถฝังลงในหมวดอะซิงก์ได้โดยตรง ทุกฟังก์ชันมี category ของมันเอง หรือก็คือสีของฟังก์ชัน และบางภาษาก็แสดงเรื่องนี้ออกมาตรง ๆ นี่เป็นตัวเลือกด้านการออกแบบภาษา และทฤษฎีหมวดหมู่ก็ทรงพลังเกินกว่าแค่เรื่องเธรด วิธีแบบ Java และแนวทางอิงเธรดจะต้องเผชิญปัญหาการซิงก์ ซึ่งเป็นส่วนที่ยากเป็นพิเศษ ส่วน JavaScript จำกัดตัวเองอยู่ในหมวดแบบ monadic โดยเฉพาะแนว continuation-passing

  • ตอนเห็นตัวอย่างการใช้ using ผ่านฟังก์ชัน defer รู้สึกสดใหม่มาก หลายคนอาจมองว่าเป็นเรื่องเข้าใจง่ายอยู่แล้ว แต่คิดว่าน่าพูดถึง

    • DisposableStack และ AsyncDisposableStack ที่อยู่ในข้อเสนอ using รองรับการลงทะเบียน callback ในตัว ซึ่งจำเป็นเพราะ using มีขอบเขตแบบบล็อก จึงต้องใช้เมื่อมีการข้ามสโคปหรือมีการลงทะเบียนแบบมีเงื่อนไข แต่ตัวแปร using ต้องถูกกำหนดค่าเริ่มต้นทันทีเหมือน const จึงทำ initialisation แบบมีเงื่อนไขไม่ได้ กรณีนี้จึงต้องสร้าง Stack ไว้ที่ด้านบนของฟังก์ชัน แล้วค่อยเอาทรัพยากรที่ใช้ไปใส่สแตกด้วย defer ทำให้เลื่อนจุดปล่อยทรัพยากรออกไปเป็นระดับฟังก์ชันได้ง่ายเมื่อจำเป็น

    • ให้ความรู้สึกคล้าย golang

  • คิดว่าเป็นไอเดียที่ดีมาก แต่ถึงในอนาคตอาจรวม [Symbol.dispose] กับ [Symbol.asyncDispose] ในสิ่งอย่าง web API stream ได้ แต่ในอนาคตอันใกล้คงมีแค่บาง API และบางไลบรารีที่รองรับ ส่วนที่เหลือซึ่งน่าจะเป็นส่วนใหญ่จะยังไม่รองรับ สุดท้ายจึงเกิดภาวะกลืนไม่เข้าคายไม่ออกว่าต้องผสม using กับ try/catch หรือไม่ก็เขียน try/catch ไปทั้งหมดเพื่อให้โค้ดอ่านเข้าใจง่ายกว่า จึงเสี่ยงที่ฟีเจอร์นี้จะได้ชื่อว่า "ใช้งานจริงไม่ได้" ทั้งที่จริงเป็นการออกแบบที่ดีและแก้ปัญหาจริง เพียงแต่เอาไปใช้ยาก

    • กับ API ที่ไม่รองรับฟีเจอร์นี้ ก็ใช้ DisposableStack เพื่อเอา using ไปครอบได้ เวลาใช้หลายทรัพยากรร่วมกันก็ยังง่ายกว่า try/catch มาก ขอแค่รันไทม์รองรับ ก็ใช้ได้ทันทีโดยไม่ต้องรอให้ทรัพยากรเดิมถูกอัปเดต

    • ในโลก JavaScript เรื่องแบบนี้เกิดซ้ำมา 15 ปีแล้ว ฟีเจอร์ภาษาใหม่ ๆ มักเข้าไปอยู่ในคอมไพเลอร์อย่าง Babel ก่อน จากนั้นจึงเข้ามาในสเปก และสุดท้ายต้องรออีก 3-4 ปีกว่าจะลงสู่ API ที่เสถียรและเบราว์เซอร์จริง ๆ นักพัฒนาก็คุ้นเคยกับการห่อ web API ด้วย wrapper เล็ก ๆ อยู่แล้ว และหลายครั้ง wrapper ก็ดีกว่า polyfill ไม่เคยคิดเลยว่าพอมีฟีเจอร์ภาษาใหม่ที่มีประโยชน์แล้วจะกลายเป็น "คงใช้ยาก"

    • จริง ๆ แล้วหลายฟีเจอร์มี polyfill อยู่แล้ว และใน ecosystem ของ NodeJS ส่วนใหญ่ก็ใช้แพตเทิร์นนี้ ผู้ใช้ก็แค่ปรับไวยากรณ์ด้วย transpiler ตอนเตรียมบรรยายเรื่องนี้เมื่อปีก่อนก็พบว่า NodeJS และไลบรารีหลักหลายตัวมี API ที่รองรับ Symbol.dispose อยู่พอสมควร ฝั่งฟรอนต์เอนด์อาจใช้ไม่มากเพราะมีระบบจัดการ lifecycle อยู่แล้ว แต่บางสถานการณ์ก็ยังมีประโยชน์ คิดว่าในไลบรารีทดสอบและฝั่งแบ็กเอนด์น่าจะกระจายตัวได้ดี

    • TC39 ควรให้ความสำคัญกับฟีเจอร์ภาษาระดับพื้นฐานอย่าง trait/protocol แบบ Rust ด้วย ใน Rust การนิยาม trait ใหม่และใส่ implementation ค่อนข้างทำได้ง่าย ขณะที่ JS เป็นภาษาดีเนมิกและมี unique symbol จึงน่าจะนำแนวคิดนี้เข้ามาได้ง่ายกว่ามาก แม้จะมีข้อเสียอย่าง orphan rule แต่ก็พัฒนาไปเป็นโครงสร้างที่ยืดหยุ่นกว่ามากได้

    • ในโลก JavaScript ปกติก็แก้ด้วย polyfill กันอยู่แล้ว

  • ทำให้นึกถึง C# โดยมี IDisposable และ IAsyncDisposable ที่มีประโยชน์มากกับ abstraction อย่างการจัดการล็อก คิว และการจัดการ temporary scope

    • ผู้เขียนข้อเสนอมาจาก Microsoft จึงทำให้ไวยากรณ์ออกมาคล้าย C# และใน GitHub issue ที่เกี่ยวข้องก็มีบริบทต่อเนื่องแบบเดียวกัน

    • โดยพื้นฐานแล้วเป็นดีไซน์ที่ยืมมาจาก C# ข้อเสนอต้นฉบับยังอ้างอิง Python context manager, Java try-with-resources และ C# using statement ด้วย ทั้งคีย์เวิร์ด using และเมธอด hook สำหรับ dispose ก็บอกใบ้ชัดเจนอยู่แล้ว

  • เข้าใจว่า JavaScript ต้องรักษา backward compatibility แต่ไวยากรณ์ [Symbol.dispose]() ดูแปลก ๆ เหมือนมี method handle อยู่ในอาร์เรย์ เลยสงสัยว่ามันคืออะไรและอยากศึกษาเพิ่ม

    • มีคำอธิบายว่าคีย์แบบไดนามิกที่ครอบด้วยวงเล็บเหลี่ยมทางซ้ายใน object literal นั้นถูกใช้มาตั้งแต่ ES6 เกือบ 10 ปีแล้ว อีกทั้ง symbol ก็อ้างด้วยสตริงไม่ได้ จึงต้องผสมคีย์แบบไดนามิกกับ method shorthand มองว่าโดยพื้นฐานแล้วมันไม่ใช่ไวยากรณ์ใหม่

    • พร้อมแหล่งอ้างอิงที่ดี โดยอธิบายว่านี่สืบเนื่องมาจากวิธีใส่คีย์แบบ symbol ให้กับอ็อบเจ็กต์เดิม ถือเป็นพัฒนาการที่เป็นธรรมชาติ

    • คนอื่นอธิบายไปแล้วว่ามันคืออะไร แต่ดูเหมือนยังไม่มีใครอธิบายว่าทำไม การใช้ Symbol เป็นชื่อเมธอดช่วยรับประกันได้ว่าเป็น API ใหม่โดยไม่ชนกับเมธอดเดิม และช่วยป้องกันไม่ให้คลาสถูกมองว่าเป็น disposable โดยไม่ตั้งใจ

    • มีการพูดถึงแนวคิด dynamic property access ว่าพร็อพเพอร์ตีของอ็อบเจ็กต์เข้าถึงได้ทั้งด้วยจุด (.) และวงเล็บเหลี่ยม ([]) รองรับทั้งสตริงและ symbol โดย symbol เป็นอ็อบเจ็กต์เฉพาะตัวที่ใช้เทียบกัน และ well known symbol อย่าง [Symbol.dispose] ก็ทำให้ขยายความสามารถได้ คล้ายกับเมธอด __dunder__ ของ Python

    • ไวยากรณ์นี้ถูกใช้มาหลายปีแล้ว iterator ของ JavaScript ก็ใช้รูปแบบเดียวกัน และถูกนำเข้ามาเกือบ 10 ปีแล้ว

  • มีการเล่าว่าเหตุใดจึงพยายามนำ structured concurrency เข้ามาใน JS โดยเฉพาะเมื่อการจัดการทรัพยากรและ lexical scope เป็นหัวใจสำคัญ พร้อมแชร์ไลบรารี structured concurrency ที่เกี่ยวข้อง

  • Bun เวอร์ชัน 1.0.23 ขึ้นไปรองรับฟีเจอร์นี้แล้ว ลองเล่นแบบ experimental ได้

  • สงสัยจริง ๆ ว่าด้วยสไตล์โค้ดที่ซับซ้อนแบบนี้จะเข้าใจและควบคุมกระแสการทำงานของโปรแกรมได้อย่างไร

    • นั่นแหละประเด็น 90% ของงานเว็บดีเวลอปเมนต์คือการอัปเกรดที่ไร้ประโยชน์หรือไม่มีใครต้องการ แล้วค่อยใช้เวลาอีก 10% แก้ปัญหาที่มันสร้างขึ้นมา มีโอกาสต่ำมากที่ใครสักคนจะต้องกลับไปอ่านโค้ดเก่า แล้วถ้าเกิดขึ้นมาก็แนะนำให้ทิ้งบั๊กไว้เป็นโจทย์ฝึกหัดสำหรับพนักงานใหม่ แม้แต่ระบบ legacy อายุ 20 ปีก็ยังถูกใช้งานอยู่จริง

    • โค้ดที่ยกมาเป็นตัวอย่างมี syntax error ร้ายแรงหลายจุด จนห่างไกลจาก JavaScript จริง และนักพัฒนา JS ก็ไม่ได้ใช้ของปะปนกันแบบนั้นอยู่แล้ว ทั้ง while, promise chain, finally ฯลฯ ปกติจะใช้ await หรือโครงสร้างจัดการข้อยกเว้นที่เหมาะสม ไลบรารีที่ออกแบบดีจะไม่ซ้อน handler กันหลายชั้น และสามารถใช้ DisposableStack เพื่อเขียนให้กระชับขึ้นได้ ทุกวันนี้หลายกรณีก็ไม่จำเป็นต้องมี async IIFE ด้วยซ้ำ

    • ถ้าทำงานเป็นมืออาชีพกับภาษานั้นจนคุ้นกับความหมายและพฤติกรรมของคีย์เวิร์ด ก็จะอ่านโค้ดได้อย่างเป็นธรรมชาติ โปรแกรมเมอร์ Haskell ก็ชินในลักษณะเดียวกัน

    • เวลา embed โค้ดใน HN ต้องเยื้องแต่ละบรรทัดอย่างน้อย 2 ช่อง (และก็เห็นด้วยว่าโค้ดเข้าใจยาก)

    • คำแนะนำสั้น ๆ ว่าการเยื้องช่วยได้

  • สงสัยว่าทำไมไม่ใช้ destructor ของ anonymous class หรือโครงสร้างอื่นที่ไม่ใช่ Symbol หากมี Symbol สองตัว ทั้งแบบซิงก์และอะซิงก์ ก็เหมือน abstraction รั่วออกมา

    • destructor ต้องการพฤติกรรมที่คาดเดาได้ โดยเฉพาะเรื่อง cleanup ที่ชัดเจน แต่ GC ที่ซับซ้อนในปัจจุบันไม่เข้ากับแพตเทิร์นนี้ ภาษาสมัยใหม่จึงรองรับ cleanup ตาม scope แทน และทำได้หลายแบบทั้ง HoF, hook พิเศษ, การลงทะเบียน callback ฯลฯ Python เริ่มต้นจาก destructor-based refcount GC แต่เพราะข้อจำกัดจึงมี context manager ตามมา

    • destructor ในภาษาอื่นทำงานตามจังหวะของ GC จึงเชื่อถือยาก แต่เมธอด dispose ถูกเรียกอย่างชัดเจนเมื่อสโคปของตัวแปรสิ้นสุดลง จึงคาดเดาได้สำหรับงานอย่างปิดไฟล์หรือปลดล็อก เมธอดแบบ Symbol ยังช่วยหลบการชนกับฟีเจอร์เดิมด้วย และโดยทั่วไปเป็นสิ่งที่ผู้พัฒนาไลบรารีคอยดูแลก็พอ การแยกซิงก์กับอะซิงก์ต้องชัดเจน และอาจต้องใช้ไวยากรณ์ที่ดูแปลกเล็กน้อยอย่าง await using a = await b()

    • ในภาษาแบบ GC การเรียก destructor แบบซิงก์ทำได้ยาก จึงมักเป็นพฤติกรรมแบบไม่กำหนดแน่ชัด ใน JS แม้จะมี WeakRef และ FinalizationRegistry แต่แม้แต่ Mozilla เองก็ไม่แนะนำให้ใช้เพราะคาดเดาไม่ได้

    • จุดแข็งของแนวทางนี้คือใช้ได้แม้กับสิ่งที่ไม่ใช่อินสแตนซ์ของคลาส

    • JavaScript ไม่มีแนวคิด anonymous property จึงทำให้คำถามนี้ดูคลุมเครือ และมีคนยืนยันว่าแทบไม่มีทางเลือกอื่นนอกจากวิธีนี้

  • ตัวอย่างแรกในเอกสารข้อเสนอคือโค้ด try/finally สำหรับปล่อยล็อกอย่างปลอดภัย จึงสงสัยว่าแพตเทิร์นแบบนี้สำคัญเฉพาะสถานการณ์ที่รันยาวนานหรือไม่ และถ้าเป็นเบราว์เซอร์หรือ CLI ที่โปรเซสจบเพราะ error ล็อกจะถูกปล่อยหรือไม่

    • สเปกระบุไว้ว่าไม่ว่าบล็อกจะจบแบบปกติหรือจบเพราะ exception/branch/escape ก็ต้องเรียก dispose เสมอ ดังนั้น using กับ try/finally จึงเหมือนกัน การบังคับจบโปรเซสอยู่นอกขอบเขตของสเปก ECMAScript ไม่ได้เข้ามายุ่งเกี่ยว stream ในตัวอย่างเป็นอ็อบเจ็กต์ภายใน JS ดังนั้นถ้าอินเทอร์พรีเตอร์หายไป แนวคิดเรื่องล็อกก็หมดความหมายไปด้วย แต่ถ้าเป็นทรัพยากรระดับ OS อย่างหน่วยความจำหรือไฟล์ ปกติ OS จะเก็บกวาดให้รวดเดียว แม้รายละเอียดการทำงานจะต่างกันไปตามแพลตฟอร์ม

    • ถ้ามองอีกแบบ หน้าเว็บในเบราว์เซอร์ก็เป็นแอปพลิเคชันที่รันอยู่นานมาก บางครั้งนานกว่าโปรเซสเซิร์ฟเวอร์ด้วยซ้ำ เกิดข้อผิดพลาดแล้วหน้าไม่ได้ตายทันที และการจัดการ error รวมถึง exception ก็มีหลักเกณฑ์ชัดเจนผ่าน finally ส่วนใน NodeJS โดยปกติ error มักทำให้โปรเซสจบ แต่ในงานเซิร์ฟเวอร์ก็มักมีการจัดการต่างออกไป กล่าวคือฟังก์ชันสำหรับปล่อยทรัพยากรใน finally จะถูกเรียกแน่นอน

 
ahwjdekf 2025-05-18

ก็ที่ผ่านมาเราอยู่กันมาได้ดีทั้งที่แทบไม่เคยสนใจเรื่องรีซอร์ซพรรค์นี้เลยไม่ใช่เหรอ จู่ ๆ เป็นอะไรขึ้นมา?