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

NodeJS使用原生fetch上传文件至REST API报fetch failed错误

问题原因

报错核心是混用了两套不兼容的FormData实现:

  1. Node.js 18+ 全局自带的fetch(底层是undici)配套的是Web标准FormData,这套API只接受字符串、Blob/File类型的字段值,没有getHeaders()方法,也不能直接识别fs.createReadStream()返回的Node.js原生可读流。
  2. 你代码里调用的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 11:33:12