如何将FormData中的多文件转为JS对象并正确发送API请求?
解决FormData文件上传时数据丢失与后端报错问题
问题核心现象
- 构建包含
productId和多张图片的FormData后,控制台打印能看到完整数据,但转成普通对象Object.fromEntries(formData.entries())后,图片数组变成空对象 - 自定义的
fetchWrapper会将请求体转为JSON格式,导致后端返回400错误:提示"productId must be a string"和"productId should not be empty",但payload中明明存在合法的productId - 即使修正后能在控制台看到formDataObj中的文件,请求payload仍丢失文件数据
错误原因分析
- File对象无法被JSON序列化:JSON.stringify处理File对象时会直接转为空对象
{},导致文件数据完全丢失 - 请求格式不匹配:文件上传必须使用
multipart/form-data格式,但当前代码强制设置Content-Type: application/json,并且将FormData转为JSON发送,后端无法解析正确的表单数据 - 代码笔误:原循环中误用了未定义的
files和file变量,正确参数应为photo
修复步骤
1. 修正FormData构建的笔误
将循环中的files改为参数photo,file[i]改为photo[i],确保图片正确添加到FormData中
2. 直接发送FormData实例,不转普通对象
放弃Object.fromEntries转换,直接将FormData作为请求body发送,因为FormData是浏览器专门用于文件上传的格式
3. 修改fetchWrapper适配FormData请求
当body是FormData类型时,不要设置Content-Type: application/json,也不要执行JSON.stringify,浏览器会自动生成正确的multipart/form-data请求头(包含boundary)
修复后完整代码
修正后的addProductImage函数
import client from "@/lib/fetchWrapper"; export default async function addProductImage({ photo, productId, }: { photo: File[]; productId: string; }) { if (!productId || typeof productId !== "string") { throw new Error("productId must be a non-empty string."); } if (!photo || photo.length === 0) { throw new Error("No image files found."); } try { const formData = new FormData(); formData.append("productId", productId); // 修正循环变量,正确添加图片 for (let i = 0; i < photo.length; i++) { formData.append('images[]', photo[i]); } // 验证FormData内容 formData.forEach((value, key) => { console.log(key, value); }); const response = await client("v1/product-image", { method: "POST", body: formData, // 直接发送FormData // 不要手动设置Content-Type,让浏览器自动处理 }); return response; } catch (error) { console.error("Error uploading product images:", error); throw error; } }
修正后的fetchWrapper
export default function client( endpoint: string, { body, ...customConfig }: any = {} ) { const token = localStorage.getItem("ecommerceToken") || sessionStorage.getItem("ecommerceToken"); // 初始化headers,默认只设置Authorization,不强制Content-Type const headers: Record<string, string> = { Authorization: "" }; if (token) { headers.Authorization = `Bearer ${token}`; } const config = { method: body ? "POST" : "GET", ...customConfig, headers: { ...headers, ...customConfig.headers, }, }; if (body) { // 如果body是FormData,直接赋值,不做JSON序列化 if (body instanceof FormData) { config.body = body; } else { // 普通JSON请求才做序列化并设置Content-Type config.headers["content-type"] = "application/json"; config.body = JSON.stringify(body); } } return fetch(`${process.env.NEXT_PUBLIC_API_URL}/${endpoint}`, config).then( async (response) => { if (response.ok) { return await response.json(); } else { const error = await response.json(); return Promise.reject(new Error(error?.message)); } } ); }
验证要点
- 发送请求前,控制台打印的FormData应包含
productId和所有图片文件 - 在浏览器开发者工具的Network面板中,查看请求的
Content-Type应为multipart/form-data; boundary=----WebKitFormBoundaryXXXX(由浏览器自动生成) - 查看请求Payload,应能看到
productId字段和每个图片的文件信息(文件名、大小等) - 后端将能正确解析
productId和图片文件,不再返回400错误
内容的提问来源于stack exchange,提问作者Rikky Bhai
相关产品推荐
相关产品推荐

