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

Firebase云函数返回RangeError:调用栈溢出问题求助

解决Firebase HTTPS可调用函数栈溢出(Maximum call stack size exceeded)问题

这个栈溢出错误的核心原因是Firebase在序列化你的函数返回值时遇到了循环引用。具体来说,你的代码返回的是once()方法生成的Promise,而这个Promise最终会解析为Firebase的DataSnapshot对象——这个对象内部存在循环引用(比如它的ref属性指向数据库引用,而数据库引用又关联着Snapshot相关的结构),导致lodash的mapValues在递归编码时陷入无限循环,最终触发栈溢出。

代码问题分析

你使用了once("value", lookupSnapshot => { ... })的回调形式,此时once()返回的Promise会直接解析为lookupSnapshot(也就是DataSnapshot对象),而你在回调里返回的update() Promise并没有被正确链式传递出去。换句话说,你的函数最终返回的是一个解析为DataSnapshot的Promise,而不是解析为update操作结果的Promise。Firebase尝试把这个DataSnapshot序列化为JSON时,就会因为循环引用引发栈溢出。

修复方案

1. 改用Promise链式调用替代回调参数

把once("value", 回调函数)的写法改成.once("value").then(回调函数),这样你在then里返回的update() Promise会成为整个Promise链的最终返回值,避免返回带有循环引用的DataSnapshot对象。

2. 确保返回可JSON序列化的值

update()方法返回的Promise会解析为一个空对象(或成功确认信息),这是可以被正常序列化的。

修改后的完整代码如下:

export const scrapeAssignments = functions.https.onCall((data, context) => {
  const htmlString = data.htmlString;
  if (!(typeof htmlString === 'string') || htmlString.length === 0) {
    throw new functions.https.HttpsError('invalid-argument', 'The function must be called with one argument "htmlString"');
  }

  const userId = getUserIdFromCallableContext(context);
  console.log("userId", userId);

  let newAssignments: ScrapedAssignment[] = [];
  try {
    newAssignments = parseAssignment(htmlString);
  } catch (e) {
    const error = <Error>e;
    throw new functions.https.HttpsError('not-found', 'parsing error: ' + error.message);
  }

  // 改用.then()链式调用,替代回调参数
  return admin.database().ref("lookup").child(userId).child("assignments")
    .once("value")
    .then(lookupSnapshot => {
      const oldAssignmentsLookup = lookupSnapshot.val() || {};
      const newAssignmentsLookup = makeLookup(newAssignments);

      // 1. Create update values for scraped assignment data
      let scrapedAssignmentUpdateValues = newAssignments.reduce((prev, current) => {
        const prefixed = prefixObject(current.id + "/", current);
        return { ...prev, ...prefixed };
      }, {});

      // 2. Use the diff from the two lookups to find old assignments to delete
      const removeAssignmentsValues = {};
      Object.keys(oldAssignmentsLookup).forEach(assignmentId => {
        if (isUndefined(newAssignmentsLookup[assignmentId])) {
          removeAssignmentsValues[assignmentId] = null;
        }
      });

      // 3. Add other user values to newly found assignments
      Object.keys(newAssignmentsLookup).forEach(assignmentId => {
        if (isUndefined(oldAssignmentsLookup[assignmentId])) {
          const doneKey = assignmentId + "/done";
          scrapedAssignmentUpdateValues[doneKey] = false;
        }
      });

      const combinedValues = { ...scrapedAssignmentUpdateValues, ...removeAssignmentsValues };
      // 返回update的Promise,链式传递
      return admin.database().ref("userAssignments").child(userId).update(combinedValues);
    })
    .catch(reason => {
      throw new functions.https.HttpsError('internal', 'Database reason: ' + reason);
    });
});

额外检查点

  • 确认combinedValues里的所有值都是普通JSON类型(字符串、数字、布尔、普通对象/数组),没有循环引用。300个键值对的数量完全在Firebase可调用函数的返回值限制内(单返回值最大10MB),所以数量不是问题。
  • 检查prefixObject和makeLookup函数是否生成了带有循环引用的对象,这也可能导致序列化时栈溢出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:32:18