Express请求挂起排查:我的Node.js服务代码哪里出错了?
我基于Express编写了封装文件系统方法的Node.js API服务,使用Thunder Client或Postman发送请求时,请求始终处于处理状态无法完成。控制台日志显示请求流程已走完(输出finished api),但客户端接收不到响应。使用系统为macOS 14 Sonoma,请帮忙排查代码问题。
代码实现
export default class Server { private app: Express; public port: number = 3000; constructor() { if (!isNode) { throw new Error("The server must be initialized on the server side"); } this.app = express(); } public init() { this.port = this.port++; if (this.port > 3050) { console.warn( "The server has already tried to start on 50 different ports. It is reccomended to open a port in the range of 3000 to 3050 for the best performance." ); } if (this.port > 3100) { throw new Error( "Attempted to start the server on 100 different ports. None were open, aborting." ); } try { this.app.listen(this.port++); this.api(); } catch (err: any) { if (err.code === "EADDRINUSE") { this.init(); } throw err; } } private async api() { console.log("api called"); const catMethods = { GET: [ "access", "lstat", "stat", "statfs", "readFile", "readdir", "readlink", "realpath", "watch", "opendir", "glob", "constants" ], PUT: [ "chmod", "chown", "lchmod", "lchown", "utimes", "lutimes", "truncate", "writeFile", "appendFile" ], POST: ["mkdir", "mkdtemp", "open", "symlink", "link", "copyFile", "cp"], DELETE: ["rmdir", "unlink", "rename"] }; const fsMethod = ( // @ts-ignore // Just copying types from express req: Request<{}, any, any, QueryString.ParsedQs, Record<string, any>>, // @ts-ignore // Just copying types from express res: Response<any, Record<string, any>, number>, catMethod: string[] ) => { console.log("fs method called"); if ( typeof req.headers.method !== "string" || typeof req.headers.args !== "string" ) { return res .status(422) .json({success: false, error: "Invalid header encoding not found"}); } const method = req.headers.method; if (!method) { return res .status(422) .json({success: false, error: "Method not found"}); } console.log("obtained headers"); const args = JSON.parse(req.headers.args || "[]"); let matchedMethod = false; for (let i = 0; i < catMethod.length; i++) { if (method === catMethod[i]) { matchedMethod = true; } } if (!matchedMethod) { return res .status(405) .json({success: false, error: "Method not allowed"}); } console.log("found match"); try { this.method(method, args); console.log("ran method"); } catch (err: any) { return res.status(500).json({success: false, error: err}); } console.log("finished api"); }; this.app.get("/fs", async (req, res) => { console.log("get"); fsMethod(req, res, catMethods.GET); }); this.app.post("/fs", async (req, res) => { fsMethod(req, res, catMethods.POST); }); this.app.put("/fs", async (req, res) => { fsMethod(req, res, catMethods.PUT); }); this.app.delete("/fs", async (req, res) => { fsMethod(req, res, catMethods.DELETE); }); } } const server = new Server(); server.init(); console.log("server started");
控制台日志
# 请求前 api called server started # 请求后 get fs method called obtained headers found match ran method finished api
问题分析与修复方案
1. 核心问题:未向客户端发送响应
Express请求必须通过res.send()、res.json()等方法明确结束,否则请求会一直处于挂起状态。你的fsMethod函数在try块执行成功后,没有任何返回响应的逻辑,导致客户端一直等待。
修复:
在try块中执行完this.method后,添加成功响应:
try { const result = await this.method(method, args); // 如果是异步方法要加await console.log("ran method"); return res.status(200).json({success: true, data: result}); } catch (err: any) { return res.status(500).json({success: false, error: err.message}); }
2. 异步方法未处理等待
如果this.method内部调用了Node.js的异步文件系统API(比如fs.promises.readFile),当前代码同步调用会导致console.log("ran method")提前执行,但实际操作还未完成,同时也无法获取返回结果。
修复:
确保this.method是异步函数,在调用时添加await,同时将fsMethod改为异步函数:
const fsMethod = async ( req: Request<{}, any, any, QueryString.ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>, number>, catMethod: string[] ) => { // ... 原有逻辑 try { const result = await this.method(method, args); console.log("ran method"); return res.status(200).json({success: true, data: result}); } catch (err: any) { return res.status(500).json({success: false, error: err.message}); } }
3. 端口初始化逻辑错误
this.port = this.port++是无效的赋值,后置自增运算符会先返回this.port的原值,再自增,所以这行代码相当于没有改变this.port的值。此外this.app.listen(this.port++)再次自增,导致实际监听的端口和预期不符。
修复:
修改端口自增逻辑:
public init() { // 先检查当前端口是否超出范围 if (this.port > 3050) { console.warn("The server has already tried to start on 50 different ports. It is reccomended to open a port in the range of 3000 to 3050 for the best performance."); } if (this.port > 3100) { throw new Error("Attempted to start the server on 100 different ports. None were open, aborting."); } const server = this.app.listen(this.port, (err?: any) => { if (err) { if ((err as any).code === "EADDRINUSE") { this.port++; this.init(); } else { throw err; } } else { console.log(`Server running on port ${this.port}`); this.api(); } }); }
4. listen错误处理无效
this.app.listen()是异步方法,错误无法通过try/catch捕获,需要通过回调函数或监听server对象的error事件处理端口占用问题。
修复:
上面的端口初始化修复已经包含了正确的错误处理逻辑,通过listen的回调函数处理端口占用,自动重试下一个端口。
内容的提问来源于stack exchange,提问作者Ethan

