Fetch上传文件的正确姿势与常见问题解决方案

百里树涵 交互 阅读 1,451
赞 14 收藏
二维码
手机扫码查看
反馈

Fetch上传的几种姿势,我更喜欢用这个

最近在做文件上传的功能,刚好涉及到用Fetch API实现上传。这东西说简单也简单,但真要玩出点花样来还挺有意思。今天就聊聊几种常见的Fetch上传方案,我个人比较偏好的是FormData + Fetch这套组合拳。

Fetch上传文件的正确姿势与常见问题解决方案

先上代码,看看不同方案长啥样

咱们直接看代码吧,毕竟开发者都是代码控嘛:

// 方案一:最基础的Blob上传
const uploadBlob = async (file) => {
  const response = await fetch('https://jztheme.com/upload', {
    method: 'POST',
    body: file,
    headers: {
      'Content-Type': file.type
    }
  });
  return response.json();
}

// 方案二:FormData上传
const uploadFormData = async (file) => {
  const formData = new FormData();
  formData.append('file', file);
  
  const response = await fetch('https://jztheme.com/upload', {
    method: 'POST',
    body: formData
    // 不用手动设置Content-Type
  });
  return response.json();
}

// 方案三:Base64编码上传
const uploadBase64 = async (file) => {
  const reader = new FileReader();
  const base64 = await new Promise((resolve) => {
    reader.onload = () => resolve(reader.result.split(',')[1]);
    reader.readAsDataURL(file);
  });

  const response = await fetch('https://jztheme.com/upload', {
    method: 'POST',
    body: JSON.stringify({ file: base64 }),
    headers: { 'Content-Type': 'application/json' }
  });
  return response.json();
}

谁更好用?谁更容易踩坑?

从实际使用来看,这几个方案各有优劣,但FormData绝对是我的心头好。为啥呢?听我慢慢道来。

首先是Blob方案,看起来挺优雅对吧?直接传文件对象就行。但这里有个大坑:后端必须能正确处理Content-Type。我就踩过这个坑,后端同事说接收到的数据格式不完整,折腾了好久才发现是因为某些浏览器会自动修改Content-Type头信息。所以现在除非特殊情况,我不太爱用这个方案。

Base64看着很酷,把文件转成字符串多方便啊。但实际上这个方案有俩硬伤:第一是性能问题,特别是上传大文件时,内存占用飙升;第二是传输效率低,Base64编码会让数据量增加约33%。上次做个图片上传功能,用户传了个5MB的图片,结果发现请求体变成了快7MB,差点没把我整崩溃。

反观FormData,简直就是万金油。首先它不用手动设置Content-Type,浏览器会自动加上正确的boundary参数;其次兼容性极好,基本不用担心后端解析问题;最重要的是支持多文件上传特别方便:

formData.append('file1', file1);
formData.append('file2', file2);

性能对比:差距比我想象的大

说到性能,我还专门做过个测试。用同样的10MB文件,分别用这三种方案上传,结果让我有点意外:

  • Blob方式:平均耗时1.2秒
  • FormData:平均耗时1.3秒
  • Base64:平均耗时2.8秒

看到没?Base64不仅数据量大,处理速度也明显慢。FormData虽然比Blob稍微慢一点点,但在可接受范围内,而且考虑它的易用性和稳定性,这点性能损失完全可以忽略。

我的选型逻辑:看场景,灵活选择

虽说我是FormData的死忠粉,但也不得不承认有时候其他方案也有它的价值。比如:

  • 如果后端要求特定的Content-Type,可能需要直接用Blob
  • 对于小文件或者需要特殊处理的场景,Base64还是能派上用场的
  • 如果你要用到Progress事件(显示上传进度),FormData配合XMLHttpRequest其实更合适

不过话说回来,在90%的常规场景下,我都推荐用FormData + Fetch这套组合。尤其是现在有了AbortController,连取消上传都变得超级简单:

const controller = new AbortController();
const signal = controller.signal;

const upload = async (file) => {
  const formData = new FormData();
  formData.append('file', file);

  try {
    const response = await fetch('https://jztheme.com/upload', {
      method: 'POST',
      body: formData,
      signal // 关键在这里
    });
    return response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('上传已取消');
    }
  }
}

// 取消上传
controller.abort();

最后唠叨几句

以上就是我对Fetch上传的一些实战心得。总的来说,强烈推荐FormData方案,特别是在多文件上传、表单提交等场景下优势明显。当然技术选型永远要看具体场景,没有银弹解决方案。

另外提醒几个容易踩的坑:

  • 别忘了处理网络错误和超时
  • 大文件上传要考虑分片
  • 记得在beforeunload里处理未完成的上传

以上是我个人对Fetch上传的完整讲解,有更优的实现方式欢迎评论区交流。对了,下次准备写一篇关于大文件分片上传的实战分享,感兴趣的可以关注一下。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论