- ข้อเสนอ 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 ความคิดเห็น
await using data = await fn()ปาฏิหาริย์ที่
awaitปรากฏอยู่ทั้งฝั่งซ้ายและฝั่งขวาพลังพิเศษใหม่ของ JavaScript: การจัดการทรัพยากรแบบชัดเจน
https://typescriptlang.org/docs/handbook/…
ความเห็นบน 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 จะถูกเรียกแน่นอน
ก็ที่ผ่านมาเราอยู่กันมาได้ดีทั้งที่แทบไม่เคยสนใจเรื่องรีซอร์ซพรรค์นี้เลยไม่ใช่เหรอ จู่ ๆ เป็นอะไรขึ้นมา?