กลับไปหน้าบทความ
#TypeScript#neverthrow#Error Handling#Backend#Result Pattern

รู้จัก neverthrow: จัดการ Error Flow ใน TypeScript ให้อ่านง่ายและชัดเจน

neverthrow เป็นไลบรารีสำหรับ TypeScript ที่ช่วยทำให้การจัดการข้อผิดพลาดเป็นส่วนหนึ่งของ type อย่างชัดเจน ด้วยแนวคิด Result ที่แยกสถานะสำเร็จและผิดพลาดออกจากกัน ทำให้โค้ดอ่านง่าย คาดเดาได้ และออกแบบ error flow ได้ดีขึ้นใน

27 มิถุนายน 2569อ่านประมาณ 2 นาที

แชร์บทความ

รู้จัก neverthrow: จัดการ Error Flow ใน TypeScript ให้อ่านง่ายและชัดเจน

รู้จัก neverthrow: จัดการ Error Flow ใน TypeScript ให้อ่านง่ายและชัดเจน

เวลาเขียน TypeScript หลายคนอาจเริ่มต้นได้อย่างสวยงาม แต่พอระบบเริ่มมี error flow ที่ซับซ้อน โค้ดก็มักจะอ่านยากขึ้นทันที โดยเฉพาะเมื่อในโปรเจกต์เดียวกันมีทั้ง try/catch, การ throw, การ return null และ return undefined ปะปนกันไปหมด สุดท้ายคนที่มาอ่านโค้ดต่อจะต้องคอยเดาเองว่าฟังก์ชันไหนมีโอกาสพัง และควร handle จุดไหนเป็นพิเศษ

หนึ่งในเครื่องมือที่ช่วยแก้ปัญหานี้ได้ดีคือ neverthrow ไลบรารีขนาดเล็กสำหรับ TypeScript ที่ใช้แนวคิดแบบ Result Pattern เพื่อทำให้การจัดการข้อผิดพลาดชัดเจนขึ้นตั้งแต่ระดับ type

ปัญหาของการจัดการ error แบบเดิม

ในหลายโปรเจกต์ business logic มักกระจัดกระจายเพราะรูปแบบการส่งต่อข้อผิดพลาดไม่สม่ำเสมอ เช่น

  • บางฟังก์ชัน throw error
  • บางฟังก์ชัน return null
  • บางฟังก์ชัน return undefined
  • บางจุดใช้ try/catch ครอบไว้หลายชั้น

เมื่อรูปแบบไม่ชัดเจน คนอ่านโค้ดต้องใช้ความคุ้นเคยหรือเดาเอาเองว่าจุดไหนอาจล้มเหลวได้บ้าง ซึ่งเป็นสาเหตุให้ review โค้ดยากขึ้น และเพิ่มโอกาสที่บาง error path จะไม่ถูก handle อย่างครบถ้วน

neverthrow คืออะไร

neverthrow คือไลบรารีที่ช่วยให้ฟังก์ชันคืนค่ากลับมาในรูปแบบที่สื่อสถานะอย่างชัดเจน แทนที่จะโยน exception ออกไป ฟังก์ชันจะคืนค่าเป็นผลลัพธ์ 2 แบบหลักคือ

  • Ok สำหรับกรณีสำเร็จ
  • Err สำหรับกรณีผิดพลาด

แนวคิดนี้ทำให้ทั้งคนเขียนและคนอ่านเห็นได้ทันทีว่า ฟังก์ชันนี้มีโอกาสผิดพลาด และจำเป็นต้องถูก handle อย่างตั้งใจ ไม่ปล่อยให้ไปพังเอาใน runtime อย่างเดียว

ทำไม Result Pattern ถึงช่วยให้โค้ดดีขึ้น

เมื่อ error ถูกทำให้เป็นส่วนหนึ่งของ type ข้อดีที่ตามมาคือความชัดเจนของระบบจะเพิ่มขึ้นอย่างมาก

  • โค้ดมีความ predictable มากขึ้น
  • editor และ type system ช่วยเตือนว่าคุณ handle กรณีผิดพลาดครบหรือยัง
  • ลดการกระจายของ try/catch ใน business flow
  • ทำให้ code review ง่ายขึ้น เพราะเห็นเส้นทางสำเร็จและล้มเหลวชัดเจน

ตัวอย่างแนวคิดที่เข้าใจง่าย เช่น ฟังก์ชันโหลดผู้ใช้

  • ถ้าเจอข้อมูล ก็ return Ok(user)
  • ถ้าหาไม่เจอ ก็ return Err('USER_NOT_FOUND')
  • ถ้าฐานข้อมูลล่ม ก็ return Err('DB_ERROR')

ในรูปแบบนี้ error ไม่ได้เป็นพฤติกรรมแอบแฝงอยู่หลังการ throw อีกต่อไป แต่กลายเป็นสิ่งที่ระบบ type มองเห็นได้โดยตรง

การใช้งานใน service layer ที่อ่านง่ายขึ้น

จุดที่ neverthrow มักแสดงประโยชน์ชัดมากคือใน service layer หรือส่วนที่มีหลายขั้นตอนต่อเนื่องกัน

จากเดิมที่อาจเขียนเป็นลักษณะนี้

  • try ดึงข้อมูล
  • catch แล้วแปลง error
  • จากนั้น throw ต่อไปอีกชั้น

เมื่อใช้ neverthrow แต่ละขั้นสามารถต่อกันด้วยเมธอดอย่าง map, andThen, และ match ได้ ทำให้ flow คล้ายการต่อท่อข้อมูล อ่านจากบนลงล่างแล้วเข้าใจง่ายกว่าเดิมมาก

โดยเฉพาะ andThen นั้นเหมาะมากสำหรับงานที่มีหลายขั้นตอนและแต่ละขั้นมีโอกาสล้มเหลว เช่น

  • เช็ก token
  • หา user
  • ตรวจสิทธิ์
  • บันทึกข้อมูล
  • ส่ง response

ถ้าขั้นตอนไหนพลาด ระบบจะหยุดที่ Err ตรงนั้นทันที ไม่ต้องเขียน if ซ้อนหลายชั้น และไม่ต้องโยน throw ข้ามไปมาหลายระดับ

ออกแบบ error ให้มีความหมายมากกว่า string

อีกข้อดีที่หลายทีมชอบคือ Err ไม่จำเป็นต้องเก็บแค่ข้อความ string เท่านั้น แต่สามารถเก็บเป็น object ที่มีข้อมูลครบขึ้นได้ เช่น

  • code
  • message
  • cause

ตัวอย่างแนวคิด เช่น

  • Err({ code: 'PAYMENT_FAILED', message: 'charge failed' })
  • Err({ code: 'EMAIL_DUPLICATE', message: 'email already used' })

การออกแบบแบบนี้ช่วยให้

  • log ได้ง่ายขึ้น
  • แยกประเภทปัญหาได้ดีขึ้น
  • map ไปเป็น HTTP status ในฝั่ง API ได้ตรงไปตรงมา
  • ส่ง code เดิมไปให้ frontend ใช้แสดงข้อความที่เหมาะกับผู้ใช้ได้ทันที

เหมาะกับงานแบบไหน

neverthrow เหมาะมากกับงานที่มี business flow ชัดเจนและมีเงื่อนไขหลายจุด เช่น

  • backend
  • API
  • domain logic
  • use case ที่มีหลายขั้นตอน
  • validation
  • authorization
  • lookup
  • payment flow

โดยเฉพาะในระบบที่ต้องการสร้างมาตรฐานเรื่อง error handling ร่วมกันทั้งทีม การใช้ Result Pattern จะช่วยให้ทุกคนคิดเรื่องเส้นทางล้มเหลวตั้งแต่ตอนออกแบบฟังก์ชัน ไม่ใช่เพิ่งมารับมือทีหลัง

สิ่งสำคัญที่ neverthrow บังคับให้ทีมคิด

สิ่งที่ทำให้ neverthrow น่าใช้จริง ๆ ไม่ได้มีแค่ syntax ที่ดูสะอาด แต่คือมันบังคับให้ทีมออกแบบ error path อย่างเป็นระบบ

หลายบั๊กในระบบไม่ได้เกิดจาก logic ตอนสำเร็จ แต่เกิดจาก logic ตอนล้มเหลวที่ไม่มีใครเขียนไว้ให้ชัด เมื่อใช้ Result Pattern จุดที่อาจพังจะถูกเปิดเผยออกมาอย่างตรงไปตรงมา และต้องถูกตัดสินใจว่าจะ handle อย่างไร

ข้อควรระวังในการใช้งาน

แม้แนวทางนี้จะมีข้อดีมาก แต่ก็ไม่ได้แปลว่าควรใช้กับทุกอย่างโดยไม่เลือก เพราะถ้าใช้เกินความจำเป็น โค้ดอาจมี ceremony มากเกินไปและดูหนักโดยไม่คุ้มค่า

แนวทางที่เหมาะคือใช้กับจุดที่มี business meaning ชัดเจน เช่น

  • validation
  • authorization
  • การค้นหาข้อมูล
  • payment flow

ในทางกลับกัน error บางประเภทก็ยังเหมาะกับการ throw อยู่ เช่น

  • bug ที่ไม่ควรเกิด
  • infrastructure failure ระดับรุนแรง
  • process crash

ดังนั้นแนวคิดที่สมดุลคือ

  • error ที่คาดเดาได้ใน business flow เหมาะกับ Result
  • error ที่เป็นความผิดปกติระดับระบบหรือ bug ยังเหมาะกับการ throw

สรุป

neverthrow เป็นเครื่องมือที่ช่วยให้ TypeScript สื่อเส้นทางของข้อผิดพลาดได้ชัดขึ้น ลดการเดา ลดการหลุด handle และทำให้โค้ดอ่านง่ายขึ้นอย่างเห็นผล โดยเฉพาะเมื่อโปรเจกต์เริ่มใหญ่และมี business logic ซับซ้อนมากขึ้น

จุดเด่นสำคัญไม่ได้อยู่แค่ความสวยของโค้ด แต่อยู่ที่การทำให้ error กลายเป็นส่วนหนึ่งของการออกแบบฟังก์ชันอย่างจริงจัง เมื่อทีมมองเห็นได้ชัดว่าฟังก์ชันไหนพังได้บ้าง การพัฒนา การ review และการดูแล codebase ในระยะยาวก็จะมีคุณภาพมากขึ้น

บางครั้งโค้ดที่ดูเป็นระเบียบ ไม่ได้ยอดเยี่ยมเพราะเขียนสั้นที่สุด แต่ยอดเยี่ยมเพราะมันบอกไว้อย่างชัดเจนว่า มีอะไรผิดพลาดได้บ้าง และควรรับมืออย่างไร