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

Express中Zod请求验证自定义中间件的ESLint问题排查与优化

Zod请求验证中间件ESLint错误排查与优化

错误原因

ESLint规则@typescript-eslint/no-misused-promises触发的核心原因是:你传递给中间件的控制器函数返回了Promise,但Express中间件的回调位置预期返回void。

具体细节:

  • Express的中间件/控制器回调默认类型定义为返回void,而你的控制器是异步函数(带async关键字),必然返回Promise
  • 自定义中间件doValidatedRequest内部直接调用了这个异步控制器,但既没有用await等待其完成,也没有捕获Promise的rejection,ESLint判定这属于“在预期返回void的位置返回了Promise”的误用场景

潜在问题

  1. 未处理的Promise拒绝:异步控制器抛出错误时,若中间件未捕获Promise的rejection,会导致Node.js进程崩溃(Express不会自动处理未捕获的异步错误)
  2. 类型定义不匹配:中间件的类型系统未明确支持异步控制器,导致TypeScript类型检查与ESLint规则冲突
  3. 验证逻辑的异步兼容问题:若Zod验证使用了异步解析(比如parseAsync),中间件同步执行的话会导致验证未完成就提前调用控制器

优化方案

1. 修正中间件的异步处理逻辑

让中间件本身成为异步函数,统一处理验证和控制器的异步流程,并捕获所有错误传递给Express错误处理中间件:

import { z } from 'zod';
import { Request, Response, NextFunction } from 'express';

// 定义类型安全的请求类型
type TypedRequest<Params = unknown, Query = unknown, Body = unknown> = Request<Params, unknown, Body, Query>;

export function doValidatedRequest<
  Params extends z.ZodTypeAny,
  Query extends z.ZodTypeAny,
  Body extends z.ZodTypeAny,
  ResBody
>(schemas: {
  params?: Params;
  query?: Query;
  body?: Body;
  response?: z.ZodTypeAny;
}) {
  return async (req: TypedRequest, res: Response<ResBody>, next: NextFunction) => {
    try {
      // 执行Zod验证(支持异步解析)
      if (schemas.params) req.params = await schemas.params.parseAsync(req.params);
      if (schemas.query) req.query = await schemas.query.parseAsync(req.query);
      if (schemas.body) req.body = await schemas.body.parseAsync(req.body);

      // 类型断言为验证后的请求类型,确保控制器能拿到类型安全的数据
      const typedReq = req as TypedRequest<z.infer<Params>, z.infer<Query>, z.infer<Body>>;
      // 调用异步控制器并等待完成
      await typedReq.controller(typedReq, res);

      // 响应验证(如果定义了响应Schema)
      if (schemas.response && res.headersSent) {
        // 重写res.json方法实现响应验证
        const originalJson = res.json;
        res.json = (data) => {
          schemas.response.parse(data);
          return originalJson.call(res, data);
        };
      }

      // 如果控制器未主动结束响应,调用next进入下一个中间件
      if (!res.headersSent) next();
    } catch (err) {
      // 将验证错误或控制器错误传递给Express全局错误处理中间件
      next(err);
    }
  };
}

2. 明确控制器的类型定义

给控制器添加严格的类型标注,避免类型歧义,让TypeScript和ESLint都能正确识别异步函数的返回值:

// 类型安全的控制器类型
type TypedController<Params, Query, Body, ResBody> = (
  req: TypedRequest<Params, Query, Body>,
  res: Response<ResBody>
) => Promise<void>;

// 示例控制器
const someFunction: TypedController<{ id: string }, { page: number }, { name: string }, { data: string }> = async (req, res) => {
  // 类型安全访问req.params.id、req.query.page、req.body.name
  res.json({ data: `Hello ${req.body.name}` });
};

3. 调整ESLint规则(可选)

如果需要同时兼容同步和异步控制器,可以在ESLint配置中针对Express中间件场景放宽规则:

{
  "@typescript-eslint/no-misused-promises": [
    "error",
    {
      "checksVoidReturn": {
        "attributes": false,
        "functions": false
      }
    }
  ]
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 12:02:08