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”的误用场景
潜在问题
- 未处理的Promise拒绝:异步控制器抛出错误时,若中间件未捕获Promise的rejection,会导致Node.js进程崩溃(Express不会自动处理未捕获的异步错误)
- 类型定义不匹配:中间件的类型系统未明确支持异步控制器,导致TypeScript类型检查与ESLint规则冲突
- 验证逻辑的异步兼容问题:若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
相关产品推荐
相关产品推荐

