在异步回调中调用Express app.use()是否为最佳实践?
你现在把app.use()写在异步回调里的写法绝对算不上良好实践,而且不止是编码规范的问题,是真的会引出实际线上故障。
现有写法的实际问题
- 最直接的是时序风险:你现在先调用
app.listen(81)启动服务,再等异步回调挂载/login路由。服务启动后到JWT签名完成、路由挂载成功的这段时间窗口里,所有打向/login的请求都会直接返回404。本地测试时因为你手动触发请求的时机晚,JWT早就签完了所以测不出问题,一旦上了生产环境,服务启动探针、刚启动就打过来的用户请求全都会报错。 - 隐藏的逻辑bug:你现在的写法只会在服务启动时执行一次
signJWT(),之后所有访问/login的用户拿到的都是同一个JWT。等这个JWT1小时过期后,只要服务不重启,所有请求拿到的都是永久失效的token,完全不符合JWT签发的正常逻辑——你之前把签名逻辑写在路由处理器里时,每次请求生成新token的逻辑才是对的。 - 可维护性差:后续如果有更多路由依赖异步初始化的资源,你会陷入多层异步回调嵌套挂载路由的混乱状态,路由注册顺序完全不可控,出问题根本没法排查。
符合模块化要求的最优方案
核心思路很简单:路由同步挂载,异步逻辑放到请求处理流程里执行,完全不需要把路由处理器堆在app.js里,也不用搞工厂函数包一层路由传参。
你可以按下面的方式拆分代码:
- 先把JWT相关的逻辑抽成独立工具模块,不要堆在入口文件里
// utils/jwt.js const jose = require("jose"); const { createSecretKey } = require("crypto"); // 密钥只在模块加载时初始化一次就行,不用每次签名都生成 const secretKey = createSecretKey("MySecret", "utf-8"); async function signJWT() { return await new jose.SignJWT({ "urn:example:claim": true }) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("1h") .sign(secretKey); } module.exports = { signJWT };
- 路由文件里直接引入依赖的工具方法,保持路由逻辑内聚,不需要做什么参数包装
// routes.js const express = require("express"); const router = express.Router(); const { signJWT } = require("./utils/jwt"); // 异步逻辑直接写在路由处理器里,用async/await就行,错误记得传给next走全局错误处理 router.get("/", async (req, res, next) => { try { const jwt = await signJWT(); res.json({ jwt }); } catch (err) { next(err); } }); module.exports = router;
- 入口文件
app.js里只做中间件、路由的同步挂载,所有逻辑清晰可控,最后再启动服务
// app.js const express = require("express"); const app = express(); const path = require("path"); const loginRoute = require("./routes"); // 全局中间件 app.use(express.static(path.join(__dirname, "public"))); // 同步挂载路由,没有任何时序问题 app.use("/login", loginRoute); // 所有路由、中间件全部挂载完成后,再启动端口监听 app.listen(81, () => { console.log("Listening on port 81"); });
如果你想进一步解耦,不想让路由文件直接依赖JWT工具模块,完全可以用一个简单的中间件把依赖注入到请求对象里,还是不需要异步挂载路由:
// app.js里挂载路由时注入依赖 const { signJWT } = require("./utils/jwt"); app.use("/login", (req, res, next) => { // 把签名方法挂到req上,下游路由直接用 req.signJWT = signJWT; next(); }, loginRoute);
路由里直接从req上取方法调用就行,耦合度更低。
补充说明
真遇到需要异步获取配置才能注册路由的场景(比如启动时从数据库拉取动态路由规则),也必须等所有异步操作完成、所有路由全部挂载完毕之后,再调用app.listen()启动服务,绝对不能先开端口再补挂路由。
内容的提问来源于stack exchange,提问作者Chizaram Igolo
相关产品推荐
相关产品推荐

