Loopback v3中RangeError:调用栈溢出问题排查求助
问题根源:重复绑定全局观察者导致无限循环/栈溢出
你的问题核心出在每次请求进来时都会重复给PersistedModel绑定access观察者,而且这个观察者会在所有模型操作(包括你中间件里的User.findById)时触发,最终形成循环调用,导致调用栈不断累积直到超出上限。
具体分析
- 重复绑定观察者:每次带accessToken的请求进入,你的中间件都会执行
PM.observe("access", ...),这意味着每处理一个请求就会多绑定一次全局的access观察者。随着请求量增加,观察者数量会越来越多,每次模型操作都会触发成百上千个观察者回调,直接撑爆调用栈。 - 循环调用触发:当你在中间件里调用
User.findById时,这个模型查询操作会触发PersistedModel的access观察者,而观察者回调的执行又可能间接引发新的模型操作,加上重复绑定的观察者,最终形成无限递归调用。
解决方向&代码修正
方案1:将全局观察者绑定移到应用启动时,只绑定一次
不要在请求中间件里动态绑定观察者,而是在应用启动入口(比如server/server.js)里只绑定一次:
// server/server.js - 应用启动完成后添加全局观察者 module.exports = function(app) { // 其他启动逻辑... const PM = app.registry.getModel("PersistedModel"); PM.observe("access", function(ctx, next) { // 仅当请求上下文存在accessToken时才赋值 if (ctx.req?.accessToken?.data) { ctx.options.data = ctx.req.accessToken.data; } next(); }); };
然后修改中间件,只负责加载当前用户,不再绑定观察者:
// server/middleware/store-current-user.js module.exports = function (options) { return function storeCurrentUser(req, res, next) { if (!req.accessToken) { return next(); } app.models.User.findById(req.accessToken.userId, function (err, user) { if (err) { return next(err); } if (!user) { return next(new Error('No user with this access token was found.')); } // 将用户信息挂载到req上,方便其他逻辑使用 req.currentUser = user; next(); }); }; };
方案2:放弃全局观察者,直接在请求上下文传递数据
如果不需要给所有模型操作传递accessToken.data,可以直接把数据挂载到req对象上,在需要的远程方法或模型钩子中通过ctx.req获取:
// 中间件里直接挂载数据 module.exports = function (options) { return function storeCurrentUser(req, res, next) { if (!req.accessToken) { return next(); } // 直接把accessToken数据挂载到req上 req.tokenData = req.accessToken.data; // 加载用户信息(如果业务需要) app.models.User.findById(req.accessToken.userId, function (err, user) { if (err) { return next(err); } if (!user) { return next(new Error('No user with this access token was found.')); } req.currentUser = user; next(); }); }; };
在远程方法中使用示例:
// 某模型的远程方法 MyModel.myRemoteMethod = function(ctx, callback) { const tokenData = ctx.req.tokenData; const currentUser = ctx.req.currentUser; // 业务逻辑处理... }; MyModel.remoteMethod('myRemoteMethod', { accepts: [{arg: 'ctx', type: 'object', http: {source: 'context'}}], // 其他配置... });
方案3:针对特定模型绑定观察者(而非所有PersistedModel)
如果只有部分模型需要这个数据,不要给PersistedModel加全局观察者,而是给单个模型单独绑定,且只绑定一次:
// common/models/my-model.js - 在目标模型文件中添加观察者 module.exports = function(MyModel) { MyModel.observe('access', function(ctx, next) { if (ctx.req?.accessToken?.data) { ctx.options.data = ctx.req.accessToken.data; } next(); }); };
额外注意点
- 你的原中间件存在异步逻辑处理不严谨:
User.findById是异步操作,但回调中调用next()时没有加return,虽然这里不会直接出问题,但养成return next()的习惯可以避免后续逻辑意外执行。 - 日志中反复出现
store-current-user.js:62:25,正是你绑定观察者的代码行,这直接证明了每次请求都在重复绑定观察者,是导致栈溢出的直接诱因。
内容的提问来源于stack exchange,提问作者uday214125
相关产品推荐
相关产品推荐

