NodeJS使用原生fetch上传文件至REST API报fetch failed错误
问题原因
报错核心是混用了两套不兼容的FormData实现:
- Node.js 18+ 全局自带的
fetch(底层是undici)配套的是Web标准FormData,这套API只接受字符串、Blob/File类型的字段值,没有getHeaders()方法,也不能直接识别fs.createReadStream()返回的Node.js原生可读流。 - 你代码里调用的
formData.getHeaders()是第三方form-datanpm包的专属方法,你写代码时大概率没有显式引入这个npm包,直接new FormData()拿到的是全局原生Web版FormData,把Node流塞进去之后,undici解析请求体时无法迭代流对象,直接抛出object2 is not iterable错误。
另外你手动拼formData.getHeaders()的写法本身也不符合原生fetch的使用规范:原生fetch处理FormData请求体时,会自动生成带正确boundary参数的Content-Type请求头,手动设置头反而会丢失boundary导致服务端解析失败。
可行解决方案
选其中一种即可,不要混写。
方案1:纯原生实现(无需额外依赖,适合小文件上传)
不用安装form-data包,直接用全局原生API,注意不要手动设置multipart头,文件转成Blob后再append:
const fs = require('node:fs/promises'); async function uploadDocumentToHub(hub_entity_id, document_path) { // 读取文件转成标准Blob对象 const fileBuffer = await fs.readFile("ap_test_document.pdf"); const fileBlob = new Blob([fileBuffer], { type: 'application/pdf' }); const formData = new FormData(); formData.append("type", "Document"); formData.append("name", "ap_test_document.pdf"); // append Blob时第三个参数传文件名 formData.append("file", fileBlob, "ap_test_document.pdf"); formData.append("entity_object_id", hub_entity_id); const raw_response = await fetch(urls.attachments, { method: "POST", headers: { Authorization: auth_code // 禁止手动加Content-Type头,fetch会自动生成带正确boundary的头 }, body: formData, }); // 按需处理响应 const resData = await raw_response.json(); console.log('上传成功', resData); }
注意:这个方案会把整个文件读入内存,传GB级大文件时内存压力大,小文件场景足够稳定简单。
方案2:流式上传(适合大文件,兼容你之前的createReadStream写法)
显式引入你之前用的form-datanpm包(没装的话先跑npm i form-data),不要用全局原生FormData,用包自带的getHeaders方法生成正确的请求头:
const fs = require('node:fs'); // 显式引入第三方form-data包,不要依赖全局FormData const FormData = require('form-data'); async function uploadDocumentToHub(hub_entity_id, document_path) { const formData = new FormData(); formData.append("type", "Document"); formData.append("name", "ap_test_document.pdf"); formData.append("file", fs.createReadStream("ap_test_document.pdf")); formData.append("entity_object_id", hub_entity_id); const raw_response = await fetch(urls.attachments, { method: "POST", headers: { Authorization: auth_code, // 这里用第三方包生成的multipart头,自带正确boundary ...formData.getHeaders() }, body: formData, }); const resData = await raw_response.json(); console.log('上传成功', resData); }
你之前Postman生成的axios代码能跑通,本质是axios内部自动适配了第三方form-data包的流格式,和方案2的逻辑一致。
常见踩坑提醒
- 用原生Web FormData时绝对不要手动写
Content-Type: multipart/form-data头,缺了boundary参数的头100%会被服务端拒绝。 - 不要同时混用全局FormData和第三方form-data包的API,两套API的字段类型、方法都不兼容,混写必出问题。
- 原生FormData的append方法不支持直接传Node.js可读流,必须转成Blob/File或者Web标准ReadableStream才能正常识别。
内容的提问来源于stack exchange,提问作者Harrison Broadbent
相关产品推荐
相关产品推荐

