基于NodeJS+Express+Firebase的电商购物车栈溢出错误求助
最大调用栈溢出错误:NodeJS+Express+Firebase实时数据库购物车功能问题
我用NodeJS(ExpressJS)和Firebase实时数据库开发带购物车功能的在线商城,点击“ADD TO CART”按钮时触发Maximum call stack size exceeded错误,已在多个平台搜索解决方案但未解决,求助。
运行环境
- 操作系统:
Windows 11 - IDE:
Webstorm 2020.1 - 开发平台:
NodeJS+ExpressJS - 数据库:Firebase实时数据库
相关代码
购物车POST接口代码
router.post("/cart", (req, res, next) => { const uid = req.cookies.UID; const itemId = req.body.productId; onValue(ref(database, "users/" + uid), (snapshot) => { if(snapshot.exists()) { let user = snapshot.val(); let cart = user.cart; if(!cart) cart = []; let index = cartIncludes(cart, itemId); if(index == -1) { cart.push({ id: itemId, amount: 1}); } else { cart[index].amount += 1; } set(ref(database, "users/" + uid + "/cart"), cart); res.redirect("/cart"); } else { res.redirect("/"); } }); });
注:cartIncludes函数仅用于验证购物车中是否存在对应商品
首页GET接口代码(错误栈涉及文件)
router.get('/', function(req, res, next) { const uid = req.cookies.UID; onValue(ref(database, "users/" + uid), (snapshot) => { let logDiv, cartDiv; let btn_dis = ""; if(snapshot.exists()) { logDiv = logoutDiv; cartDiv = _cartDiv; } else { logDiv = loginRegDiv; cartDiv = ""; btn_dis = 'disabled="disabled"'; } let productsDiv = ""; onValue(ref(database, "products"), (snapshots) => { snapshots.forEach((childSnapshot) => { const childKey = childSnapshot.key; const childData = childSnapshot.val(); let title = childData.title; let description = childData.description; let price = childData.price; let id = childKey; productsDiv += `<div class="outer"><div class="content animated fadeInLeft"><h4>${title}</h4><p id="desc">${description}</p><a>${price}$</a><div class="button"><form action="/cart" method="post"><input type="hidden" name="productId" value="${id}"><button type="submit" ${btn_dis}>ADD TO CART</button></form></div></div></div>`; }); res.render("index", { logDiv, cartDiv, productsDiv }); }); }); });
错误信息
[2022-07-24T01:01:29.273Z] @firebase/database: FIREBASE WARNING: Exception was thrown by user callback. RangeError: Maximum call stack size exceeded at new View (C:\Users\User\WebstormProjects\store\node_modules\express\lib\view.js:94:20) at Function.render (C:\Users\User\WebstormProjects\store\node_modules\express\lib\application.js:570:12) at ServerResponse.render (C:\Users\User\WebstormProjects\store\node_modules\express\lib\response.js:1008:7) at C:\Users\User\WebstormProjects\store\routes\index.js:58:11 at CallbackContext.onValue (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:12493:31) at C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:13349:46 at exceptionGuard (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:674:9) at eventListRaise (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:11316:13) at eventQueueRaiseQueuedEventsMatchingPredicate (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:11291:17) at eventQueueRaiseEventsAtPath (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:11263:5) C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:685 throw e; ^ RangeError: Maximum call stack size exceeded at new View (C:\Users\User\WebstormProjects\store\node_modules\express\lib\view.js:94:20) at Function.render (C:\Users\User\WebstormProjects\store\node_modules\express\lib\application.js:570:12) at ServerResponse.render (C:\Users\User\WebstormProjects\store\node_modules\express\lib\response.js:1008:7) at C:\Users\User\WebstormProjects\store\routes\index.js:58:11 at CallbackContext.onValue (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:12493:31) at C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:13349:46 at exceptionGuard (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:674:9) at eventListRaise (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:11316:13) at eventQueueRaiseQueuedEventsMatchingPredicate (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:11291:17) at eventQueueRaiseEventsAtPath (C:\Users\User\WebstormProjects\store\node_modules\@firebase\database\dist\index.node.cjs.js:11263:5)
问题根源及解决方案
核心问题:无限监听循环
你使用的onValue方法会持续监听指定数据库路径的变化,每次数据更新都会触发回调函数:
- 在POST接口中,监听
users/${uid}路径后,回调里修改users/${uid}/cart的数据,这会再次触发onValue回调 - 回调重复执行又会修改cart数据,形成无限循环,最终导致调用栈溢出
- 首页GET接口同样存在问题:
onValue持续监听数据变化,未取消监听可能导致内存泄漏或重复渲染
修复步骤
POST接口替换监听为单次获取:用
get方法替代onValue,仅获取一次数据,避免循环触发
修改后代码:router.post("/cart", async (req, res, next) => { const uid = req.cookies.UID; const itemId = req.body.productId; try { const snapshot = await get(ref(database, "users/" + uid)); if(snapshot.exists()) { let user = snapshot.val(); let cart = user.cart || []; let index = cartIncludes(cart, itemId); if(index === -1) { cart.push({ id: itemId, amount: 1}); } else { cart[index].amount += 1; } await set(ref(database, "users/" + uid + "/cart"), cart); res.redirect("/cart"); } else { res.redirect("/"); } } catch (error) { console.error(error); res.status(500).send("服务器错误"); } });首页接口优化为单次获取:同样用
get替代onValue,首页仅需加载时获取一次数据
修改后代码:router.get('/', async function(req, res, next) { const uid = req.cookies.UID; try { let logDiv, cartDiv; let btn_dis = ""; const userSnapshot = await get(ref(database, "users/" + uid)); if(userSnapshot.exists()) { logDiv = logoutDiv; cartDiv = _cartDiv; } else { logDiv = loginRegDiv; cartDiv = ""; btn_dis = 'disabled="disabled"'; } let productsDiv = ""; const productsSnapshot = await get(ref(database, "products")); productsSnapshot.forEach((childSnapshot) => { const childKey = childSnapshot.key; const childData = childSnapshot.val(); let title = childData.title; let description = childData.description; let price = childData.price; let id = childKey; productsDiv += `<div class="outer"><div class="content animated fadeInLeft"><h4>${title}</h4><p id="desc">${description}</p><a>${price}$</a><div class="button"><form action="/cart" method="post"><input type="hidden" name="productId" value="${id}"><button type="submit" ${btn_dis}>ADD TO CART</button></form></div></div></div>`; }); res.render("index", { logDiv, cartDiv, productsDiv }); } catch (error) { console.error(error); res.status(500).send("服务器错误"); } });额外建议:
- 若需实时监听数据,使用
onValue后要在请求结束或页面卸载时调用off方法取消监听,避免内存泄漏 - 购物车更新可考虑使用Firebase的
transaction方法,避免并发更新冲突
- 若需实时监听数据,使用
内容的提问来源于stack exchange,提问作者Matan
相关产品推荐
相关产品推荐

