【问题标题】:Firebase Function: Unhandled error RangeError: Maximum call stack size exceededFirebase 函数:未处理的错误 RangeError:超出最大调用堆栈大小
【发布时间】:2019-01-24 17:18:38
【问题描述】:

我有以下可调用函数,它从我的数据库中获取一些数据,然后使用 html-pdf 使用该数据创建一个 PDF,将该 PDF 上传到存储,最后返回存储中文件的名称。 它以 https 形式运行良好,但我想将其转换为可调用函数,由于某种原因我无法弄清楚,它会因以下错误而崩溃: RangeError: maximum call stack size exceeded。

我怀疑这与 html-pdf 不适用于 Promise 而是使用错误/数据回调这一事实有关。但我试图将其转换为一个没有成功的承诺。

export const createPdf = functions.https.onCall((data, context) => {
    const refId = data.refId;
    const companyId = data.companyId;
    const userId = context.auth.uid;

    return admin.database().ref('/references').child(companyId).child(refId).once('value', (snapshot) => {
        const filePath = '/references/' + refId + '/pdfs/' + refId + '.pdf';
        const localeId = snapshot.child('locale').val();

        return admin.database().ref('/tags').child(localeId).once('value', (tagsSnapshot) => {
            const jsLocaleId = localeId.replace(/_/, "-");
            const projectDate = moment().locale(jsLocaleId)
                .year(snapshot.child('year').val())
                .month(snapshot.child('month').val() - 1)
                .date(15)
                .format('MMMM YYYY');

            const tags = tagsSnapshot.val();
            const projectCategories = ...
            const pictures = snapshot.child('pictures').val();

            const pdfData = {
                projectName: snapshot.child('projectName').val(),
                surface: snapshot.child('surface').val(),
                companyName: snapshot.child('companyName').val(),
                date: projectDate,
                newBuilding: snapshot.child('newBuilding').val(),
                customerName: snapshot.child('customerName').val(),
                categories: projectCategories,
                address: snapshot.child('address').val().replace(/\n/g, '<br>'),
                satellite: snapshot.child('satellite').val(),
                pictures: !isNullOrUndefined(pictures) ? pictures.map((item) => {
                    return {url: item}
                }) : []
            };
            console.log("data", pdfData);
            const options = {...};

            const localTemplate = path.join(os.tmpdir(), 'share.html');
            const localPDFFile = path.join(os.tmpdir(), 'share.pdf');
            const languageCode = localeId.split("_")[0];

            return admin.storage().bucket().file('/templates/share-' + languageCode + '.html').download({destination: localTemplate}).then(() => {
                const source = fs.readFileSync(localTemplate, 'utf8');
                const html = handlebars.compile(source)(pdfData);
                pdf.create(html, options).toFile(localPDFFile, function (err, result) {
                    if (err) {
                        console.log(err);
                        throw new functions.https.HttpsError('internal', err.message);
                    }

                    return admin.storage().bucket().upload(localPDFFile, {
                        destination: filePath,
                        resumable: false,
                        metadata: {contentType: 'application/pdf'}
                    }).then((files) => {
                        console.log("files", files);
                        return files[0].getMetadata().then((metadata) => {
                            const name = metadata[0]["name"];
                            return {
                                name: name
                            };
                        });
                    }).catch(error => {
                        console.error(error);
                        throw new functions.https.HttpsError('internal', "Could not upload PDF because " + error.message);
                    });
                });
            }).catch((error) => {
                console.error("Could not download template");
                throw new functions.https.HttpsError('internal', error.message);
            });
        });
    });
});

【问题讨论】:

    标签: typescript firebase google-cloud-functions


    【解决方案1】:

    可调用函数不应该只返回任何承诺。他们应该返回一个承诺,该承诺通过发送给客户端的响应来解决。你的返回一个承诺,当数据库操作完成时解决。 Cloud Functions 可能正在尝试序列化 promise 中包含的值(一个 DataSnapshot 对象)。这可能包含在序列化期间导致问题的循环引用。

    看起来您假设返回嵌套三个 promise 的 promise 会将响应发送给客户端,但这不是 promise 的工作方式。你可以在 HTTP 函数中解决这个问题,因为你可以调用 response.send() 深层嵌套,但这在这里行不通。您将不得不取消嵌套所有承诺并连续运行它们。 (你现在所做的被认为是不符合承诺的风格。)

    【讨论】:

    • 非常感谢 Doug,为我解决了这个问题。我只是从每个 then 块返回 promise 并将 then 链接在一起,我为几个 then 块所需的所有变量声明了函数级变量,并围绕对 html-pdf 的调用构建了一个新的 Promise 并且我的函数没有不会再失败了。现在,即使它没有失败,客户端也会因为 PDF 创建时间过长而超时,而且似乎超时为 10 秒且不可配置。所以我可能不得不坚持使用 https 功能。但是,现在我对 Promises 的理解更好了。
    • 您能否详细说明“可调用函数不应该只返回任何承诺。它们应该返回一个承诺,该承诺通过发送给客户端的响应来解决。”一点点?我正在尝试使用可调用对象,几乎我对返回任意大小数组的 twilio api 所做的一切都会引发相同的错误。
    • @regretoverflow 我在使用 twilio api 时遇到了同样的问题,这对我有用:pastebin.com/bWYGFFcC - 基本上我只是返回一个新的承诺来解决我明确需要的任何数据
    • 你能添加一个示例承诺代码吗?我开始使用 firebase 可调用函数和 Promise。
    【解决方案2】:

    可调用函数无法解析深度数据,您必须将请求stringify,并在后端解析

    前端

    const string = JSON.stringify(data)
    functions.httpsCallable("your-function-name")({string});
    

    后端

    const requestData = JSON.parse(data.string)
    

    【讨论】:

      猜你喜欢
      • 2021-01-27
      • 2019-03-04
      • 2021-04-06
      • 2021-03-04
      • 2019-05-04
      • 1970-01-01
      • 2018-06-03
      • 2021-09-21
      • 2013-07-08
      相关资源
      最近更新 更多