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

React+dropzone上传千级文件报ERR_INSUFFICIENT_RESOURCES如何解决

问题根因

net::ERR_INSUFFICIENT_RESOURCES 报错是资源耗尽导致的,你的代码存在三个核心问题:

  1. 看似用asyncForEach逐个处理,实际上循环里只做了「创建axios请求、把Promise塞进数组」的操作,没有等待请求执行完成就进入下一轮循环,等于上千个请求几乎同时并发发起,直接打满浏览器TCP连接池和内存配额
  2. 一次性给上千个文件创建ObjectURL预览图,内存占用随文件数线性上涨,很容易触发浏览器内存上限
  3. 直接操作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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 09:33:30