You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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方法会持续监听指定数据库路径的变化,每次数据更新都会触发回调函数:

  1. 在POST接口中,监听users/${uid}路径后,回调里修改users/${uid}/cart的数据,这会再次触发onValue回调
  2. 回调重复执行又会修改cart数据,形成无限循环,最终导致调用栈溢出
  3. 首页GET接口同样存在问题:onValue持续监听数据变化,未取消监听可能导致内存泄漏或重复渲染

修复步骤

  1. 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("服务器错误");
        }
    });
    
  2. 首页接口优化为单次获取:同样用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("服务器错误");
      }
    });
    
  3. 额外建议:

    • 若需实时监听数据,使用onValue后要在请求结束或页面卸载时调用off方法取消监听,避免内存泄漏
    • 购物车更新可考虑使用Firebase的transaction方法,避免并发更新冲突

内容的提问来源于stack exchange,提问作者Matan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 18:49:14