React+dropzone上传千级文件报ERR_INSUFFICIENT_RESOURCES如何解决
问题根因
net::ERR_INSUFFICIENT_RESOURCES 报错是资源耗尽导致的,你的代码存在三个核心问题:
- 看似用
asyncForEach逐个处理,实际上循环里只做了「创建axios请求、把Promise塞进数组」的操作,没有等待请求执行完成就进入下一轮循环,等于上千个请求几乎同时并发发起,直接打满浏览器TCP连接池和内存配额 - 一次性给上千个文件创建
ObjectURL预览图,内存占用随文件数线性上涨,很容易触发浏览器内存上限 - 直接操作DOM更新上传状态,React场景下额外增加性能开销,文件数多的时候卡顿明显
可落地方案
核心是控制上传并发数,同一时间最多只允许固定数量的请求在执行(建议设为4-8,匹配浏览器同域名并发连接上限),跑完一个补一个,避免资源被瞬间打满,同时修复内存和渲染问题。
1. 实现并发控制工具函数
替换你原来的asyncForEach,这个函数会严格限制同时运行的异步任务数量:
// 异步并发池:tasks为待执行的异步任务函数数组,limit为最大并发数 async function asyncPool(tasks, limit = 6) { const results = []; const executing = new Set(); for (const task of tasks) { const promise = Promise.resolve().then(() => task()); results.push(promise); executing.add(promise); const clean = () => executing.delete(promise); promise.then(clean).catch(clean); if (executing.size >= limit) { await Promise.race(executing); } } return Promise.all(results); }
2. 重构上传逻辑
不要在循环里直接发起请求,先把每个文件的上传逻辑封装成待执行的函数,再交给并发池调度,同时把上传状态收归React state管理,禁止直接操作DOM:
onDrop: async acceptedFiles => { stateChanger(true) // 初始化文件列表,内置上传状态,不提前生成预览URL const initialFiles = acceptedFiles.map(file => ({ ...file, uid: uuidv4(), status: 'pending' // 状态可选值:pending/uploading/success/error })); setFiles(initialFiles); const uploadUrl = `${API_URL}/projects/upload`; // 封装上传任务,注意这里返回的是函数,不是直接执行的Promise const uploadTasks = initialFiles.map(file => { return () => { // 标记当前文件为上传中 setFiles(prev => prev.map(f => f.uid === file.uid ? {...f, status: 'uploading'} : f)) const formData = new FormData(); formData.append('file', file); formData.append('projectId', id); formData.append('rootfolder', folder); return axios.post(uploadUrl, formData) .then(() => { setFiles(prev => prev.map(f => f.uid === file.uid ? {...f, status: 'success'} : f)) }) .catch(() => { setFiles(prev => prev.map(f => f.uid === file.uid ? {...f, status: 'error'} : f)) }) } }); // 启动并发上传,最大并发数设为6,可根据实际带宽、服务端承载调整 await asyncPool(uploadTasks, 6); // 所有文件上传完成 stateChanger(false) }
3. 修复预览渲染和内存问题
调整缩略图渲染逻辑,不要一次性生成所有预览URL,渲染时按需生成、加载完立刻回收,同时根据state展示上传状态:
const thumbs = files.map(file => { let previewUrl = ''; // 仅图片类型生成预览 if (file.type.startsWith('image/')) { previewUrl = URL.createObjectURL(file); } const statusMap = { pending: '待上传', uploading: '上传中', success: '上传成功', error: '上传失败' }; return ( <div className="thumb" key={file.uid}> <div className="thumbList"> {previewUrl && ( <img src={previewUrl} alt={file.name} width={40} height={40} // 图片加载完成立刻回收ObjectURL,避免内存泄漏 onLoad={() => URL.revokeObjectURL(previewUrl)} /> )} <span>{file.path} - {file.size} bytes</span> <span className={`upload-${file.status}`}>{statusMap[file.status]}</span> </div> </div> ) });
同时删掉原来useEffect里批量回收所有preview URL的逻辑,避免和按需回收逻辑冲突。
额外优化建议(千级文件场景必做)
- 当文件总数超过100时,接入虚拟列表组件,只渲染可视区域内的缩略图条目,避免上千个DOM节点导致页面卡顿
- 增加失败重试逻辑:单个文件上传失败后自动重试2-3次,降低网络波动导致的失败率
- 利用axios的
onUploadProgress回调,把单文件上传进度存在state里,展示更精细的进度提示 - Dropzone配置里增加文件大小、类型校验,提前拦截不符合要求的文件,减少无效请求
内容的提问来源于stack exchange,提问作者alexmorgan.cr
相关产品推荐
相关产品推荐

