You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Express Controller中实现DRY?重复Catch代码的中间件优化问询

Fixing Duplicate Catch Blocks in Express Controllers with Middleware

Absolutely! This is such a common frustration when working with async/await in Express controllers—all those repetitive try/catch blocks just clutter up your code and make it harder to maintain. The good news is middleware is perfect for solving this, and it’s straightforward to set up.

Step 1: Create an Async Handler Wrapper

First, we’ll make a utility function that wraps your async controller functions and automatically catches errors, passing them to Express’s error-handling pipeline. This way, you never have to write a catch block in your controllers again.

// utils/asyncHandler.js
const asyncHandler = (fn) => (req, res, next) => {
  // Wrap the async function in Promise.resolve to handle both resolved and rejected promises
  Promise.resolve(fn(req, res, next)).catch(next);
};

module.exports = asyncHandler;

Step 2: Refactor Your Controllers

Now, instead of wrapping every controller in try/catch, just pass your async function through the asyncHandler:

Before (with duplicate catch blocks):

// controllers/userController.js
exports.getUsers = async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (err) {
    res.status(500).json({ error: 'Failed to fetch users' });
  }
};

exports.getUserById = async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.json(user);
  } catch (err) {
    res.status(500).json({ error: 'Failed to fetch user' });
  }
};

After (clean, no duplicate catches):

// controllers/userController.js
const asyncHandler = require('../utils/asyncHandler');
const User = require('../models/User');

exports.getUsers = asyncHandler(async (req, res) => {
  const users = await User.find();
  res.json(users);
});

exports.getUserById = asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    // You can still throw custom errors here if needed
    throw new Error('User not found');
  }
  res.json(user);
});

Step 3: Set Up a Global Error-Handling Middleware

Next, add a top-level middleware to handle all errors that get passed to next() (including the ones caught by our asyncHandler). This is where you’ll define your unified error response format.

Make sure this middleware is defined after all your routes and other middleware:

// app.js
const express = require('express');
const app = express();

// ... your routes, other middleware ...

// Global error handler
app.use((err, req, res, next) => {
  // Default to 500 if no status code is set
  const statusCode = err.statusCode || 500;
  // Default error message
  const errorMessage = err.message || 'Internal Server Error';

  // Send the standardized error response
  res.status(statusCode).json({
    error: errorMessage,
    // Optional: Include stack trace only in development
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Bonus: Custom Error Classes (For Better Control)

If you want to distinguish between operational errors (like "user not found") and system errors (like database connection issues), create a custom error class:

// utils/AppError.js
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    // Mark this as an operational error (not a random system error)
    this.isOperational = true;

    // Capture stack trace without including the error class itself
    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = AppError;

Then use it in your controllers:

exports.getUserById = asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    throw new AppError('User not found', 404);
  }
  res.json(user);
});

And update your global error handler to handle operational errors differently if needed:

app.use((err, req, res, next) => {
  if (err.isOperational) {
    res.status(err.statusCode).json({ error: err.message });
  } else {
    // Log system errors for debugging
    console.error('ERROR:', err);
    res.status(500).json({ error: 'Something went wrong!' });
  }
});

Why This Works

Express doesn’t automatically catch errors from async functions unless you pass them to next(). Our asyncHandler wraps the async function, catches any rejected promises, and forwards the error to next(), which then triggers our global error handler. This eliminates all those repetitive catch blocks in your controllers while keeping error handling consistent across your app.

内容的提问来源于stack exchange,提问作者Jamie Aden

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:02:39