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

如何解决Nest.js中Axios调用出现的Maximum call stack size exceeded栈溢出错误

Nest.js批处理服务调用LoopBack 4微服务时出现栈溢出错误

我用Nest.js开发了一个批处理服务,通过动态Axios工具调用LoopBack 4开发的其他微服务。其中一个API调用正常,但调用另一个API时触发如下错误:

node:buffer:1152
Buffer.prototype.slice = function slice(start, end) {
                                       ^

RangeError: Maximum call stack size exceeded

调试后发现目标API的日志显示已正常返回响应,说明问题出在Axios对响应的序列化过程中。尝试了多种方案都没解决,相关代码和配置如下:


核心调用函数 axiosApiCall

export async function axiosApiCall(fnName: string, serviceName: string, fnParams: any) {
    let config;
    let baseURL;

    switch (serviceName) {
        case 'Payment':
            // 需在环境变量中配置process.env.PAYMENT_SERVICE_URL
            // 本地测试可硬编码或设置默认值
            baseURL = process.env.PAYMENT_SERVICE_URL || 'https://api.example.com/payment-service';
            config = paymentConfig; // 使用导入的配置
            break;
        default:
            console.error(`错误:服务'${serviceName}'未配置`);
            return { status: 500, data: { message: `服务'${serviceName}'不存在` } };
    }

    if (!config || !config.operations) {
        console.error(`错误:服务'${serviceName}'配置无效或缺少'operations'字段`);
        return { status: 500, data: { message: `服务'${serviceName}'配置无效` } };
    }

    // 根据函数名查找操作模板
    const template = config.operations.find(op =>
        op.functions && Object.keys(op.functions).includes(fnName)
    );

    if (!template) {
        console.error(`错误:服务'${serviceName}'中未找到函数'${fnName}'`);
        return { status: 404, data: { message: `函数'${fnName}'不存在` } };
    }

    // 从配置中提取函数所需参数
    const requiredParams = template.functions[fnName];
    const resolvedFnParams: Record<string, any> = {};

    // 将传入的参数映射到必填参数
    for (const paramKey of requiredParams) {
        if (fnParams[paramKey] === undefined) {
            console.warn(`警告:函数'${fnName}'缺少必填参数'${paramKey}'`);
            // 若参数为严格必填,可在此抛出错误
            // throw new Error(`函数'${fnName}'缺少必填参数'${paramKey}'`);
        }
        resolvedFnParams[paramKey] = fnParams[paramKey];
    }
    console.log('模板解析用参数对象:', resolvedFnParams);

    // 替换模板中的占位符
    const finalTemplate = resolveTemplatePlaceholders(template.template, resolvedFnParams);
    console.log('解析后的最终模板:', finalTemplate);

    let url = `${baseURL}${finalTemplate.url}`;
    let query = finalTemplate.query;
    let headers = finalTemplate.headers;
    let body = finalTemplate.body; // 若来自模板,body为JSON字符串

    switch (finalTemplate.method) {
        case 'GET':
            return await safeAxiosGet(url, query, headers);

        case 'POST':
            // 若body为字符串(如配置中的JSON.stringify结果),先解析再发送
            const postData = body ? JSON.parse(body) : {};
            return await safeAxiosPost(url, postData, headers);

        case 'PATCH':
            const patchData = body ? JSON.parse(body) : {};
            return await safeAxiosPatch(url, patchData, headers);

        case 'DELETE':
            // DELETE请求通常无body,若API需要可在此添加
            return await safeAxiosDelete(url, headers);

        default:
            console.error(`错误:不支持的HTTP方法'${finalTemplate.method}'`);
            return { status: 405, data: { message: `不支持的HTTP方法'${finalTemplate.method}'` } };
    }
}

Axios工具函数与模板解析函数

async function safeAxiosGet(url: string, query?: Record<string, string>, headers: Record<string, string> = {}) {
    try {
        console.log(`发送GET请求到: ${url}`);
        console.log('查询参数:', query);
        console.log('请求头:', JSON.stringify(headers));

        const response = await axios.get(url, {
            headers,
            params: query, // Axios会处理params,空对象则无查询字符串
        });

        const resp = {
            status: response.status,
            data: response.data,
        };
        console.log('GET响应:', resp);
        return resp;
    } catch (err: any) {
        console.error('❌ Axios GET请求失败:', err.message, err.response?.data);
        throw err;
    }
}

function resolveTemplatePlaceholders(
    template: Template,
    params: Record<string, any>
): Template {
    /**
     * 单个值解析辅助函数
     * 若值为{key}格式的占位符,尝试从params中取值
     * 找到则返回对应值,否则:URL部分保留占位符,请求头/查询参数返回空字符串
     * @param value 待解析的字符串值
     * @param isUrlPart 是否为URL部分
     * @returns 解析后的字符串
     */
    const resolveValue = (value: string, isUrlPart: boolean = false): string => {
        if (value.startsWith('{') && value.endsWith('}')) {
            const key = value.slice(1, -1);
            if (params[key] !== undefined) {
                return String(params[key]); // 转为字符串保证兼容性
            } else {
                // 参数缺失时,URL部分保留占位符,其他返回空字符串避免出现undefined
                return isUrlPart ? value : '';
            }
        }
        return value;
    };

    // 解析URL占位符(路径参数)
    const resolvedUrl = template.url.replace(/{(\w+)}/g, (_, key) =>
        params[key] !== undefined ? String(params[key]) : `{${key}}`
    );

    // 解析请求头
    const resolvedHeaders: Record<string, string> = {};
    for (const [key, val] of Object.entries(template.headers)) {
        const resolvedVal = resolveValue(val);
        // 仅保留解析后非空的请求头(排除因参数缺失被替换为空的项)
        if (resolvedVal !== '') {
            resolvedHeaders[key] = resolvedVal;
        }
    }

    // 解析查询参数
    const resolvedQuery: Record<string, string> = {};
    if (template.query) {
        for (const [key, val] of Object.entries(template.query)) {
            const resolvedVal = resolveValue(val);
            // 仅保留解析后非空的查询参数
            if (resolvedVal !== '') {
                resolvedQuery[key] = resolvedVal;
            }
        }
    }

    // 解析body占位符
    let resolvedBody = template.body;
    if (template.body) {
        // 假设body是带占位符的JSON字符串
        resolvedBody = template.body.replace(/{(\w+)}/g, (_, key) => {
            if (params[key] !== undefined) {
                // 参数为对象则转为JSON字符串,否则直接转为字符串
                return typeof params[key] === 'object' ? JSON.stringify(params[key]) : String(params[key]);
            }
            return ''; // 缺失的body占位符替换为空字符串
        });
    }

    return {
        ...template,
        url: resolvedUrl,
        headers: resolvedHeaders,
        query: Object.keys(resolvedQuery).length > 0 ? resolvedQuery : undefined,
        body: resolvedBody,
    };
}

配置文件 payment-service.datasource.config.json

{
  "name": "PaymentService",
  "connector": "rest",
  "baseURL": "",
  "crud": true,
  "options": {
    "baseUrl": "",
    "headers": {
      "accept": "application/json",
      "content-type": "application/json"
    }
  },
  "operations": [
    {
      "template": {
        "method": "GET",
        "url": "/payment-gateway-masters/{id}",
        "headers": {
          "content-type": "application/json",
          "correlationid": "{correlationid}"
        },
        "query": {
          "filter": "{filter}"
        }
      },
      "functions": {
        "getPaymentGatewayMasterById": ["id", "correlationid", "filter"]
      }
    }
    ]}

可能的原因与解决方案

1. 响应数据存在循环引用

LoopBack 4返回的实体对象常包含关联关系(如父子实体互相引用),Axios默认的JSON序列化无法处理循环引用,会导致递归调用直至栈溢出。

解决方法:

  • 在Axios请求中自定义响应转换逻辑,处理循环引用:
const response = await axios.get(url, {
    headers,
    params: query,
    transformResponse: [function (data) {
        const cache = new WeakSet();
        return JSON.parse(JSON.stringify(data, (key, value) => {
            if (typeof value === 'object' && value !== null) {
                if (cache.has(value)) return undefined; // 跳过已缓存的循环引用对象
                cache.add(value);
            }
            return value;
        }));
    }]
});
  • 或者在LoopBack 4服务端优化序列化,用@JsonIgnore注解标记不需要返回的关联属性,避免循环引用出现在响应中。

2. Axios版本兼容性问题

旧版本Axios在处理特定格式或大体积响应时可能存在栈溢出bug。

解决方法:
升级Axios到最新稳定版本:

npm update axios

3. 强制以文本形式接收响应

错误出现在Buffer处理环节,可能是Axios自动转换响应时的异常。强制以文本形式接收后手动解析,绕开Buffer转换逻辑。

解决方法:

async function safeAxiosGet(url: string, query?: Record<string, string>, headers: Record<string, string> = {}) {
    try {
        console.log(`发送GET请求到: ${url}`);
        console.log('查询参数:', query);
        console.log('请求头:', JSON.stringify(headers));

        // 强制以文本形式接收响应
        const response = await axios.get(url, {
            headers,
            params: query,
            responseType: 'text'
        });

        // 手动解析JSON
        const data = JSON.parse(response.data);
        const resp = {
            status: response.status,
            data: data,
        };
        console.log('GET响应:', resp);
        return resp;
    } catch (err: any) {
        console.error('❌ Axios GET请求失败:', err.message, err.response?.data);
        throw err;
    }
}

4. 检查响应数据结构

先打印响应的原始内容,确认是否存在特殊类型(如Buffer、函数)导致序列化失败:

// 在safeAxiosGet中添加
console.log('原始响应内容:', response.data);
console.log('响应数据类型:', typeof response.data);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 19:55:53