如何解决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
相关产品推荐
相关产品推荐

