LoopBack Node.js循环更新异常:before save钩子重复触发解决方案咨询
解决LoopBack循环Patch操作中钩子递归触发的问题
兄弟,你踩的这个坑我之前也遇到过——在before save钩子里面调用upsert,结果每次更新都会再次触发同一个钩子,导致逻辑重复执行甚至无限递归,最终结果肯定不对。下面给你几个靠谱的解决方案,按推荐程度排序:
最优方案:用批量更新替代循环upsert,彻底避免钩子触发
LoopBack的updateAll方法是直接走数据库批量操作,不会触发模型的任何钩子,而且性能比循环逐条upsert高得多。直接改写你的逻辑:
module.exports = function(Model) { Model.observe('before save', async(ctx, next) => { if (ctx.data) { await updateRelatedRecords(Model); } return next(); }); } const updateRelatedRecords = async function(Model) { // 一步完成所有符合条件的记录更新,完全不触发钩子 await Model.updateAll( { property: { gt: threshold } }, // 筛选出property大于阈值的记录 { property: { inc: 1 } } // 把这些记录的property加1 ); }
顺便提一句,你原来的代码里有两个小bug:for(let entity in modelDbData)是遍历数组的索引(不是元素),应该用for...of;还有upsert(model)里的model应该是entity,这个在上面的方案里直接用批量更新就不用管这些细节了。
备选方案:给上下文加标记,跳过递归钩子
如果你的业务逻辑必须逐条处理记录(比如需要额外的自定义逻辑),那可以在第一次触发钩子时给上下文加个标记,后续钩子触发时检查到这个标记就直接跳过:
module.exports = function(Model) { Model.observe('before save', async(ctx, next) => { // 检查是否是递归触发的钩子,是的话直接跳过 if (ctx._skipRecursiveHook) return next(); if (ctx.data) { // 给后续的upsert操作打上标记,防止触发钩子递归 ctx._skipRecursiveHook = true; await updateRelatedRecords(ctx.data, Model); } return next(); }); } const updateRelatedRecords = async function(data, Model) { const modelDbData = await Model.find(); for (const entity of modelDbData) { if (entity.property > threshold) { entity.property += 1; await Model.upsert(entity); } } }
这个方案能解决递归问题,但性能不如批量更新,适合必须逐条处理的场景。
兜底方案:改用after save钩子
如果前面两个方案都不适用,可以把钩子从before save改成after save——等初始的保存操作完成后,再执行批量更新。不过要注意,批量更新的操作还是会触发钩子,所以最好结合上面的标记方法一起用:
module.exports = function(Model) { Model.observe('after save', async(ctx, next) => { // 只在新增/修改单条记录时触发,且跳过递归触发的钩子 if (ctx.instance && !ctx._skipRecursiveHook) { ctx._skipRecursiveHook = true; await updateRelatedRecords(Model); } return next(); }); } const updateRelatedRecords = async function(Model) { await Model.updateAll( { property: { gt: threshold } }, { property: { inc: 1 } } ); }
内容的提问来源于stack exchange,提问作者20B2
相关产品推荐
相关产品推荐

