Error handling mechanism and debugging
Error Handling Mechanisms and Debugging
The Express framework provides various ways to handle errors and debug applications. Whether synchronous or asynchronous errors, they need to be properly caught and handled; otherwise, they may cause application crashes or unpredictable behavior. Error-handling middleware, try-catch blocks, and global error handlers are commonly used techniques.
Synchronous Error Handling
Errors in synchronous code can be directly caught using try-catch blocks. In Express route handlers, uncaught synchronous errors are automatically caught by the framework and passed to the error-handling middleware.
app.get('/sync-error', (req, res) => {
try {
// Synchronous operation that might throw an error
const data = JSON.parse('invalid json')
res.send(data)
} catch (err) {
// Catch synchronous errors
res.status(400).send({ error: err.message })
}
})
Asynchronous Error Handling
Handling asynchronous errors requires special attention because try-catch cannot catch errors thrown in callback functions or Promise chains. Express 5.x and later automatically handle uncaught errors in async functions, but in version 4.x, explicit handling is required.
// Asynchronous error handling in Express 4.x
app.get('/async-error', (req, res, next) => {
someAsyncFunction((err, data) => {
if (err) return next(err) // Pass the error to the error-handling middleware
res.send(data)
})
})
// Better approach using async/await
app.get('/async-await', async (req, res, next) => {
try {
const data = await somePromiseFunction()
res.send(data)
} catch (err) {
next(err) // Pass the error to the error-handling middleware
}
})
Error-Handling Middleware
Error-handling middleware in Express is similar to other middleware but takes four parameters (err, req, res, next
). When defining error-handling middleware, it should be placed after all other middleware and routes.
// Basic error-handling middleware
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
// More comprehensive error handling
app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500
const message = statusCode === 500 ? 'Internal Server Error' : err.message
if (process.env.NODE_ENV === 'development') {
res.status(statusCode).json({
error: message,
stack: err.stack
})
} else {
res.status(statusCode).json({ error: message })
}
})
Custom Error Classes
Creating custom error classes helps better organize and distinguish between different types of errors, making error handling and debugging easier.
class AppError extends Error {
constructor(message, statusCode) {
super(message)
this.statusCode = statusCode
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'
this.isOperational = true
Error.captureStackTrace(this, this.constructor)
}
}
// Using the custom error
app.get('/custom-error', (req, res, next) => {
if (!req.query.id) {
return next(new AppError('ID is required', 400))
}
// ...
})
Debugging Techniques
Debugging Express applications can be done using various tools and techniques. The built-in Node.js debugger, console.log
, and specialized debugging modules are common methods.
// Using the debug module
const debug = require('debug')('app:server')
app.get('/debug', (req, res) => {
debug('Request received for /debug')
// ...
})
// Using Node.js's built-in inspector
// Start the app with: node --inspect app.js
// Then visit chrome://inspect in Chrome
Logging
A robust logging system is crucial for error tracking and debugging. Libraries like winston
and morgan
can be used.
const winston = require('winston')
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
})
// Using the logger in error-handling middleware
app.use((err, req, res, next) => {
logger.error(err.stack)
res.status(500).send('Error occurred')
})
Handling Uncaught Exceptions and Unhandled Promise Rejections
In addition to route errors, global uncaught exceptions and unhandled Promise rejections must also be handled.
// Handling uncaught exceptions
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err)
// Typically, log the error and exit gracefully
process.exit(1)
})
// Handling unhandled Promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason)
})
Enhancing Error Handling with Middleware
Third-party middleware like express-async-errors
can simplify error handling for async/await.
require('express-async-errors') // Should be called immediately after requiring Express
// Now try-catch blocks can be omitted
app.get('/simplified', async (req, res) => {
const data = await someAsyncOperation()
res.send(data)
// Any errors will automatically be passed to the error-handling middleware
})
Proper Use of HTTP Status Codes
Appropriate HTTP status codes are essential for API error handling, helping clients understand the nature of the error.
app.get('/status-codes', (req, res, next) => {
if (!req.query.token) {
return res.status(401).json({ error: 'Unauthorized' }) // 401 Unauthorized
}
if (!req.query.id) {
return res.status(400).json({ error: 'ID is required' }) // 400 Bad Request
}
try {
const resource = findResource(req.query.id)
if (!resource) {
return res.status(404).json({ error: 'Not found' }) // 404 Not Found
}
res.json(resource)
} catch (err) {
next(err) // 500 Internal Server Error
}
})
Performance Considerations
Error-handling logic itself can become a performance bottleneck, especially in high-traffic applications.
// Avoid expensive error handling in hot paths
app.use((err, req, res, next) => {
// Quickly return a basic error response
res.status(err.statusCode || 500).json({ error: err.message })
// Log detailed error information asynchronously to avoid impacting response time
setImmediate(() => {
logger.error({
message: err.message,
stack: err.stack,
request: {
method: req.method,
url: req.url,
headers: req.headers
}
})
})
})
Testing Error Handling
Ensuring error-handling logic works as expected is equally important. Write test cases for error handling.
// Using a testing framework like Jest to test error handling
describe('Error Handling', () => {
it('should return 404 for unknown routes', async () => {
const response = await request(app).get('/nonexistent')
expect(response.statusCode).toBe(404)
})
it('should handle async errors', async () => {
const response = await request(app).get('/async-error')
expect(response.statusCode).toBe(500)
expect(response.body.error).toBeDefined()
})
})
Security Considerations
Be cautious about the information exposed in error responses to avoid leaking sensitive data.
app.use((err, req, res, next) => {
// Hide technical details in production
const response = {
error: 'An error occurred'
}
if (process.env.NODE_ENV === 'development') {
response.message = err.message
response.stack = err.stack
}
res.status(500).json(response)
})
Client-Side Error Handling
For web applications, consider how to gracefully present server errors to the client.
// Example of frontend handling API errors
fetch('/api/data')
.then(response => {
if (!response.ok) {
return response.json().then(err => {
// Display different error messages based on status code
if (response.status === 401) {
showLoginModal()
} else {
showErrorToast(err.error || 'Request failed')
}
throw err
})
}
return response.json()
})
.then(data => renderData(data))
.catch(error => {
console.error('API request failed:', error)
})
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:静态文件服务与资源托管
下一篇:Cookie与Session管理