การจัดการข้อผิดพลาด หรือ Error Handler ใน Express เบื้องต้น

27 April 2019 By Ninenik Narkdee
expressjs nodejs error handler

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ expressjs nodejs error handler



เนื้อหาต่อไปนี้ เราจะมาดูในเรื่องของ Error Handler
หรือการจัดการ ควบคุม และดูแลความผิดพลาด หรือข้อผิดพลาดที่อาจจะ
เกิดขึ้นใน Express ซึ่งได้เคยเกริ่นเล็กน้อยไปแล้วในเนื้อหาเกี่ยวกับ
Middleware ฟังก์ชั่น ในบทความ ตามลิ้งค์ http://niik.in/910
    ใน Express ก็มีการจัดการข้อผิพดลาด หรือ Error โดยใช้ Middleware ฟังก์ชั่น
ประเภท Error-handling middleware ซึ่งเป็น build-in ฟังก์ชั่นที่ถูกเพิ่มไปไว้ในส่วนท้ายสุดของ
middleware ฟังก์ชั่น อื่นๆ
 

การเกิด Error ใน Express

    เรามาดูการทำงาน โดยใช้เนื้อหาต่อเนื่องจากตอนที่แล้ว http://niik.in/911
    ที่เมื่อเราเปิดเข้ามาที่หน้าแรก path: "/" ตัว app ก็จะไปโหลดไฟล์ template พร้อมแสดงข้อมูลที่เรากำหนด
ซึ่งเป็นรูปแบบการทำงานปกติ  ไฟล์ app.js เป็นดังนี้
 
const express = require('express')  // ใช้งาน module express
const app = express()  // สร้างตัวแปร app เป็น instance ของ express
const path = require('path') // เรียกใช้งาน path module
const port = 3000  // port 

// ส่วนของการใช้งาน router module ต่างๆ 
const indexRouter = require('./routes/index')

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.set('view options', {delimiter: '?'});

app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(express.static(path.join(__dirname, 'public')))

// เรียกใช้งาน indexRouter
app.use('/', indexRouter)


app.listen(port, function() {
    console.log(`Example app listening on port ${port}!`)
})
    ถ้าเราลองเข้าไปยัง path: "/user" ซึ่งไม่ได้มีการกำหนดการทำงานให้กับ route นี้ไว้ ดูว่าจะเกิดอะไรขึ้น
 
 

 
 
    จะเห็นว่า มีการแจ้ง "Cannot GET /user" เป็น build-in การแสดง error เบื้องต้น ที่ทำงานอัตโนมัติใน Express
โดยที่เราไม่ได้แทรกโค้ดใดๆ เพิ่มเข้าไป  นั่นหมายความว่า หลังจากผ่านคำสั่ง app.use() ที่ path ไม่ตรงเงื่อนไข 
ก็จะมีการ ทำคำสั่งเกี่ยวกับการจัดการ error เกิดขึ้น เพื่อจะดูการดักจับการทำงาน ก่อนที่จะไปถึงการจัดการ error
อัตโนมัติ ให้เราแทรก คำสั่งนี้ เข้าไป จะได้ไฟล์ app.js บางส่วน เป็นดังนี้
 
// เรียกใช้งาน indexRouter
app.use('/', indexRouter)

// ทำงานทุก request ที่เข้ามา
app.use(function(req, res, next) {
    throw new Error('BROKEN') // กำหนด error เอง
})
 
    ผลลัพธ์ที่ได้ 
 
 

 
 
    คำว่า "BROKEN" คือ error message (err.message) ที่เรากำหนด และส่วนที่เหลือที่แสดง เรียกว่า error stack 
(err.stack) ตัว error stack นี้จะแสดง ก็ต่อเมื่อ Express App ของเรามีการกำหนด environment variable หรือ
NODE_ENV เป็น "development" ซึ่งหมายถึงอยู่ในโหมดการพัฒนา แต่ถ้าเรากำหนด NODE_ENV เป็น "production"
ค่า error.stack จะไม่แสดง
    เรามาลองดูค่าว่าตอนนี้ NODE_ENV ของเราเป็นค่าอะไร โดยใช้คำสั่ง 
 
console.log(req.app.get('env'))
 
    จะได้เป็นดังรูป ซึ่งขณะนี้ ค่า NODE_ENV เท่ากับ "development"
 
 

 
 
    จากนั้นเราลองกำหนดค่า ให้เป็น "production" โดยใช้คำสั่ง 
 
app.set('env','production')
 
    จะได้ผลลัพธ์ดังรูป
 
 

 
 

 
 
    ในส่วนของ console ฝั่ง server เราจะเห็นค่าต่างๆ แต่ในส่วนของ ผู้ใช้ จะไม่มีการแสดง err.stack ในโหมดนี้
รวมทั้งการแจ้ง error ก็เป็นการใช้งานจากตัว build-in ใช้สถานะเป็น 500 (Internal Server Error)
    ให้เราแก้ไข "env" เป็น "development" เหมือนเดิม หรือปิดส่วนของการกำหนดค่า ไปก่อน เพื่อไปต่อในเนื้อหา
 
    ตัวอย่างการเกิด error ข้างต้น เป็นแบบ synchronous ที่เมื่อเกิด error ขึ้น ตัว Express ก็จะดักจับ error นั้น
แล้วส่งต่อไปยังส่วนจัดการ error ที่เป็น build-in ในทันที
    มาดูกรณีที่เป็น asynchronous ยกตัวอย่างการเกิด error 
 
// ทำงานทุก request ที่เข้ามา 
app.use(function(req, res, next) {
    console.log(req.app.get('env'))
    // throw new Error('BROKEN') // กำหนด error เอง
    Promise.resolve().then(function () {
        throw new Error('BROKEN')
      }).catch(next) // error จถถูกส่งไปยัง error build-in ใน Express   
})
 
    Promise Object เป็นลักษณะการทำงานแบบ asynchronous หรือก็คือเข้าใจอย่างง่าย ว่า สัญญาว่าจะทำสิ่งหนึ่ง
สิ่งใด แต่ไม่ได้ทำในทันทีขณะนั้น และเมื่อทำสิ่งหนึ่งสิ่งใดแล้ว จะแจ้งกลับมาอีกทีว่าสำเร็จหรือไม่ 
    ในโค้ด เมื่อเกิด Promise.resolve() หมายถึงเมื่อได้ทำตามที่สั่งสำเร็จเรียบแล้วแล้ว ให้ทำคำสั่งใน then()
แล้วไปจบการทำงานใน คำสั่ง catch() 
    ในคำสั่งสุดท้าย asynchronous ก็ส่ง error ไปทำงานต่อ ผ่านฟังก์ชั่น next 
    จะเห็นว่า ไม่ว่าจะเป็นการเกิด error ในแบบ synchronous หรือ asynchronous ก็จะมีการส่งต่อ error นั้นที่เกิดขึ้น
ไปทำงานต่อ
 
    กลับมาที่ปัญหาของเรา คือ ตอนนี้เมื่อ ผู้ใช้เรียกไปยัง path: "/user" ซึ่งเราไม่ได้กำหนดการทำงานให้กับ path นี้
หรือนั้นก็คือ Page Not Found เราน่าจะคุ้นกับคำนี้ สิ่งที่เราต้องการให้เกิดขึ้น หรือ error ที่เราต้องการส่งต่อไปก็คือ
404 page not found ดังนั้น เราจึงใช้ตัวช่วย สร้าง error object ผ่านการใช้งาน "http-errors" module ซึ่งเป็น Node
Module ที่เราสามารถเรียกใช้งานได้เลย ไม่ต้องติดตั้งเพิ่มเติม สามารถดูการใช้งานเพิ่มเติมได้ที่ http-errors 
    ให้เราเพิ่มการเรียกใช้ module เข้าไป
 
const createError = require('http-errors')
 
    รูปแบบการใช้งาน 
 
createError([status], [message], [properties])
 
    ตัวอย่างเช่น
 
var err = createError(404, 'This video does not exist!')
 
    ในที่นี้เราจะกำหนดแค่ status code เข้าไป
 
var err = createError(404)
 
   แล้วส่งต่อค่าไปใน error build-in middleware ฟังก์ชั่น จะได้เป็น
 
// ทำงานทุก request ที่เข้ามา 
app.use(function(req, res, next) {
    var err = createError(404)
    next(err)
})
 
    จะได้ผลลัพธ์เป็นดังนี้
 
 

 
 

 
 
    ตัว http-errors จะสร้าง error มีสถานะ err.status เป็น "404" มีข้อความเป็น err.message เป็น "Not Found"
ส่วนของข้อความ เราสามารถกำหนดคำเพิ่มไปเองก็ได้ ในที่นี้เราใช้ค่าเริ่มต้นที่ http-errors จัดให้  นอกจากนี้ในโหมด
"development" ยังมี err.stack เกิดขึ้นด้วย
 
    จากขั้นตอนข้างต้น เมื่อเราดักจับ error แล้วสร้างรูปแบบ error ด้วย http-errors module และส่งต่อเข้ามาใน Express 
ให้ตัว error-handling middleware ที่เป็น build-in ฟังก์ชั่น จัดการ ผลลัพธ์ ก็จะได้อย่างในรูปด้านบน หรือที่เรียกรูปแบบ
การจัดการดังกล่าวว่า default error handler 
    เราจะใช้งานการจัดการ error แบบกำหนดเอง กันในหัวข้อต่อไป

 
 

การจัดการ Error แบบกำหนดเอง

    การสร้าง error-handling middleware ฟังก์ชั่นขึ้นมาเองนั้น มีรูปแบบคล้ายๆ กันกับ middleware ฟังก์ชั่นอื่นๆ 
ยกเว้น middleware กรณีจัดการกับ error เราจะมี argument เพิ่มเข้ามาเป็น 4 ตัว จากปกติทั่วไปที่มีแค่ 3 นั่นก็คือ
มีตัว error ที่เราส่งเข้ามาจากคำสั่ง next ใน middleware ตัวก่อนหน้า 
    รูปแบบ error-handling middleware เบื้องต้น จะได้เป็น
 
// ส่วนจัดการ error
app.use(function (err, req, res, next) {
	console.error(err.stack)
	console.error(err.message)
	console.error(err.status)
	res.status(500).send('Something broke!')
})
 
    โค้ดข้างต้น เรายังไม่ใช้ค่า error ที่ส่งมา ไปใช้งาน แต่เราทำการ ส่ง หน้า error status 500
และข้อความไปแสดงยังหน้า path: "/user" แทน ส่วน error ที่ถูกส่งมา เราลองแสดงใน console ฝั่ง
server เพื่อดูผลลัพธ์  จะได้เป็นดังนี้
 
 

 
 

 
 
    จะเห็นค่าที่แสดงใน console.log() เป็นค่า error ที่เราส่งมา เป็นค่าที่ถูกต้อง ส่วนค่าที่เราส่งไปแสดงเป็นค่า
ทดสอบ ส่งค่าที่ต้องการไปแสดงยังหน้าผู้ใช้งาน เป็น 500 (Internal Server Error) แล้วเราก็ให้แสดงข้อความว่า
"Something broke!" ในหน้า error นั้น 
    ตอนนี้ เราเข้าใจหลักการทำงานของ ฟังก์ชั่น error-handling middleware แล้ว รู้ว่า error ที่ส่งมามีอะไรบ้าง
หรือกรณีไม่มีค่าส่งมา เราก็สามารถกำหนดค่าไปแสดงในหน้า error เป็นค่าอื่นๆ ตามต้องการได้ 
 

 

ประยุกต๋ใช้งาน Error ร่วมกับ Template Engine

    มาถึงส่วนสุดท้าย เมื่อเรารู้จักการใช้งานการจัดการ error เบื่องต้นแล้ว เราก็มาประยุกต์ เพื่อแสดงหน้า error ใน
Template Engine โดยส่งค่า error ค่าจริงไปแสดง ซึ่งเราใช้ EJS 
    ให้เราสร้างไฟล์ error.ejs ด้วยรูปแบบโค้ดดังนี้เพิ่มเข้าไป
 
<!DOCTYPE html>
<html>
  <head>
    <title><?= error.status ?> <?= message ?></title>
    <link rel='stylesheet' href='css/mycss.css' />
  </head>
  <body>
    <h1><?= message ?></h1>
    <h2><?= error.status ?></h2>
    <pre><?= error.stack ?></pre>
  </body>
</html>
 
    แก้ไขส่วนจัดการ error เป็น
 
// ส่วนจัดการ error
app.use(function (err, req, res, next) {
    // กำหนด response local variables 
    res.locals.message = err.message
    res.locals.error = req.app.get('env') === 'development' ? err : {}

    // กำหนด status และ render หน้า error page
    res.status(err.status || 500) // ถ้ามี status หรือถ้าไม่มีใช้เป็น 500
    res.render('error') 
})
 
    การกำหนดตัวแปร ชื่อ message และ error แบบ response local เป็นการกำหนดขอบเขตของตัวแปร
ทั้งสองให้สามารถใช้งานได้ เฉพาะภายไนหน้า views ที่ render ในระหว่างที่เกิด request / response cycle 
หรือก็คือสามารถใช้งานในไฟล์ error.ejs template 
 
    รูปแบบการกำหนดคือ 
 
res.locals.[ชื่อตัวแปร] = [ค่าที่ต้องการ]
 
    สำหรับค่าตัวแปร error ที่กำหนดด้วย res.locals.error นั้น มีการใส่เงื่อนไขเพิ่มเข้ามาคือ
ถ้า req.app.get('env') === 'development' หรือก็คืออยู่ในโหมด "development" ให้มีค่าเท่ากับ err object
ที่ส่งเข้ามา ซึ่งใน err object ก็จะมี err.message, err.status และ err.stack
แต่ถ้าไม่ได้อยู่ในโหมด "development" เช่น อยู่ในโหมด "production" ก็จะให้ค่า error เป็น object ว่างหรือค่าว่าง
 
res.locals.error = req.app.get('env') === 'development' ? err : {}
 
    มาจากรูปแบบ
 
if( req.app.get('env') === 'development' ) {
    res.locals.error = err
} else {
    res.locals.error = {}
}
 
    สำหรับการเรียกใช้งานตัวแปร response local variable ในไฟล์ template เราก็ใช้ชื่อตัวแปรนั้นๆ อ้างอิงได้เลย 
เช่น message ใช้เป็น <?= message ?>
ส่วน error นั่นมี property ย่อย เราก็เรียกใช้งานในรุปแบบ object เป็น 
<?= error.status ?> หรือ <?= error.stack ?> ลักษณะแบบนี้เป็นต้น
 
    การกำหนดตัวแปรแบบ response local ทำให้เราไม่ต้องส่งค่าต่างๆ เหล่านี้เข้าไปไฟล์ template ตอนใช้คำสั่ง
render() โดยตรง นอกจากนั้น ทำให้เราสามารถจัดการ กำหนดรูปแบบหรือเงื่อนไขให้กับค่าต่างๆ เหล่านี้ได้สะดวกขึ้น
    ไฟล์ app.js แบบเต็ม
 
const express = require('express')  // ใช้งาน module express
const app = express()  // สร้างตัวแปร app เป็น instance ของ express
const path = require('path') // เรียกใช้งาน path module
const createError = require('http-errors') // เรียกใช้งาน http-errors module
const port = 3000  // port 

// ส่วนของการใช้งาน router module ต่างๆ 
const indexRouter = require('./routes/index')

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.set('view options', {delimiter: '?'});
// app.set('env','production')

app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(express.static(path.join(__dirname, 'public')))

// เรียกใช้งาน indexRouter
app.use('/', indexRouter)

// ทำงานทุก request ที่เข้ามา 
app.use(function(req, res, next) {
    var err = createError(404)
    next(err)
})

// ส่วนจัดการ error
app.use(function (err, req, res, next) {
    // กำหนด response local variables 
    res.locals.message = err.message
    res.locals.error = req.app.get('env') === 'development' ? err : {}

    // กำหนด status และ render หน้า error page
    res.status(err.status || 500) // ถ้ามี status หรือถ้าไม่มีใช้เป็น 500
    res.render('error') 
})

app.listen(port, function() {
    console.log(`Example app listening on port ${port}!`)
})
 
    ดูผลลัพธ์และการทำงานในโหมด "development"
 
 

 

 
    ทดสอบเปลี่ยนโหมดเป็น "production" จะเห็นว่าจะไม่แสดงในส่วนของ error status และ error stack
 
 


 
 
    เนื้อหาในส่วนนี้ เราได้รู้จักกับ error ที่อาจจะเกิดขึ้นใน Express และการจัดการกับ error เบื้องต้น
เป็นแนวทางสำหรับทำความเข้าใจในเนื้อหาอื่นๆ ต่อไป


อย่าลืมกด Like กด Share เป็นกำลังใจ ในการสร้างบทความใหม่ๆ น่ะครับ



อ่านต่อที่บทความ









เนื้อหาที่เกี่ยวข้อง



Tags:: error handler nodejs expressjs







URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ