Skip to content
Created: March 23, 2023

1. Summary of file upload methods ​

1️⃣ Form upload (the most basic method) ​

html
<form action="/upload" method="POST" enctype="multipart/form-data">
  <input type="file" name="file" />
  <button type="submit">上传</button>
</form>

✅ Features:

  • Browser native support
  • Suitable for simple file uploads (no JS required)
  • Form enctype must be multipart/form-data

❌ Disadvantages:

  • The page will refresh (not asynchronously)
  • Difficult to control upload progress

2️⃣ Ajax asynchronous upload (mainstream front-end method) ​

Use fetch method

javascript
const formData = new FormData();
formData.append('file', fileInput.files[0]);
// 关于formData不得不说一下,formData.append的参数只有三个,当设置value时只支持字符串类型和Blob类型
// The field's value. This can be a string or Blob (including subclasses such as File). If none of these are specified the value is converted to a string.

fetch('/upload', {
  method: 'POST',
  body: formData
}).then(res => res.json());

Using XMLHttpRequest method

js
// 检查是否支持FormData
if(window.FormData) { 
  var formData = new FormData();

  // 建立一个upload表单项,值为上传的文件
  formData.append('file', document.getElementById('upload').files[0]);
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/upload');
  // 定义上传完成后的回调函数
  xhr.onload = function () {
    if (xhr.status === 200) {
      console.log('上传成功');
    } else {
      console.log('出错了');
    }
  };
  xhr.send(formData);
}

✅ Advantages:

  • No need to refresh the page
  • Support display progress and resume downloading from breakpoints
  • Can be accompanied by token or additional fields

📌 Backend receiving:

js
// Node + Express
app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ success: true });
});

3️⃣ Use third-party upload SDK (cloud storage) ​

Cloud vendorUpload methodFeatures
Alibaba Cloud OSSali-oss SDKSupports multi-part upload and signature direct upload
Tencent Cloud COScos-js-sdk-v5Supports temporary keys, breakpoint resumption
Qiniu Cloudqiniu-jsBase64 upload, form upload

✅ Advantages:

  • Reduce back-end pressure (front-end direct transmission to the cloud)
  • Supports multi-part upload and instant transfer of large files

📌 Direct transfer process:

  1. The front end requests the back end to upload credentials (signature)
  2. Front-end uploads signature to cloud storage
  3. Callback/notify backend after success

4️⃣ Large file upload in chunks (chunk upload) ​

javascript
// 切片
const chunkSize = 5 * 1024 * 1024; // 5MB
const file = input.files[0];
const chunks = [];

for (let i = 0; i < file.size; i += chunkSize) {
  chunks.push(file.slice(i, i + chunkSize));
}

Upload:

javascript
const uploadId = crypto.randomUUID(); // 同一文件的切片使用同一 ID
await Promise.all(chunks.map(async (chunk, index) => {
  const formData = new FormData();
  formData.append('uploadId', uploadId);
  formData.append('file', chunk);
  formData.append('index', index);
  formData.append('total', chunks.length);
  const res = await fetch('/upload', { method: 'POST', body: formData });
  if (!res.ok) throw new Error('Chunk upload failed: ' + index);
}));
// 全部成功后,再通知后端按 uploadId 合并;大文件还应限制上传并发数

Merge (backend sketch): isolate chunks by uploadId and store index as an integer; uploadedChunks below contains their indices and server-generated temporary paths.

js
function mergeChunks(uploadedChunks, total, targetPath) {
  const ordered = [...uploadedChunks].sort((a, b) => a.index - b.index);
  if (!Number.isInteger(total) || total < 1 ||
      ordered.length !== total || ordered.some((part, index) => part.index !== index)) {
    throw new Error('Missing or duplicate chunks');
  }
  fs.writeFileSync(targetPath, ''); // 本次合并从空文件开始,避免重复追加
  for (const part of ordered) {
    fs.appendFileSync(targetPath, fs.readFileSync(part.path));
  }
}

✅ Advantages:

  • Resume uploads by recording completed chunks on the backend; reuse the upload ID and skip existing chunks when retrying
  • Add progress reporting and concurrency control; prefer asynchronous, streaming merges for large files to avoid blocking the server

5️⃣ Base64 upload (small file scenario) ​

javascript
const reader = new FileReader();
reader.onload = () => {
  const base64 = reader.result;
  fetch('/uploadBase64', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ data: base64 })
  });
};
reader.readAsDataURL(file);

✅ Advantages:

  • Simple to implement
  • Can be directly used to store avatars, QR codes, etc.

❌ Disadvantages:

  • Volume increased by approximately 33%
  • Not suitable for large files

🧭 Upload function optimization points (interview focus) ​

DirectionsInstructions
✅ Multipart uploadSuitable for large files, supports resumed uploads
✅ Parallel uploadUpload multiple shards at the same time, saving time
✅ Instant transferCheck whether file exists through hash
✅ Upload progressUse xhr.upload.onprogress; xhr.onprogress measures response download progress
✅ Retry if failedAutomatically retransmit if network abnormality occurs
✅ Front-end verificationFile size/type restrictions
✅ Signature direct transferImprove security

2. Summary of file download methods ​

1️⃣ Direct download link (browser behavior) ​

html
<a href="https://example.com/file.pdf" download>下载文件</a>

✅ Simple and straightforward, no JS required. ⚠️ Under the same origin restriction, cross-domain may be restricted.

2️⃣ The backend returns a file stream (most common) ​

javascript
fetch('/api/download').then(res => res.blob()).then(blob => {
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'report.pdf';
  a.click();
  URL.revokeObjectURL(url);
});

You can also download files in form (only replace the dom-related operation code in the middle of the above code)

js
let myForm = document.createElement('form')
myForm.style.display = 'none'
myForm.setAttribute('target', '')
myForm.setAttribute('method', 'get')
myForm.setAttribute('action', url)
document.body.append(myForm);
myForm.submit();

📌 Backend:

js
res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"');
res.sendFile(filePath);

✅ Advantages:

  • Controllable permissions
  • Support cross-domain downloads

By the way, download the image with the desired specifications

js
function downloadImgURL(url, name) { 
  let image = new Image(); 
  image.setAttribute("crossOrigin", "anonymous"); 
  image.src = url; 
  image.onload = () => { 
    let canvas = document.createElement("canvas"); 
    canvas.width = image.width; 
    canvas.height = image.height; 
    let ctx = canvas.getContext("2d"); 
    ctx.drawImage(image, 0, 0, image.width, image.height); 
    canvas.toBlob(blob => { 
      let url = URL.createObjectURL(blob); 
      let a = document.createElement("a"); 
      a.download = name; 
      a.href = url; 
      a.click(); 
      a.remove(); // 用完释放URL对象 
      URL.revokeObjectURL(url); 
      }); 
    }; 
  }

3️⃣ Blob download + file name resolution ​

javascript
fetch('/api/download').then(async (res) => {
  if (!res.ok) throw new Error('Download failed: ' + res.status);
  const blob = await res.blob();
  const disposition = res.headers.get('Content-Disposition') || '';
  const utf8 = disposition.match(/(?:^|;)\s*filename\*\s*=\s*UTF-8'[^']*'([^;]+)/i);
  const plain = disposition.match(/(?:^|;)\s*filename\s*=\s*(?:"((?:\\.|[^"\\])*)"|([^;]+))/i);
  let filename = plain ? (plain[1] ?? plain[2].trim()).replace(/\\(["\\])/g, '$1') : 'download';
  if (utf8) {
    try {
      filename = decodeURIComponent(utf8[1].trim());
    } catch {
      // 编码无效时保留 filename 或默认文件名
    }
  }
  filename = (filename || 'download').replace(/[\/\\]/g, '_');
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  a.remove();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
}).catch(console.error);

The example prefers UTF-8 filename*, then falls back to filename or a default name. res.blob() reads the complete response; prefer a native browser download for large files. Content-Disposition.

In a regular HTTP response, the Content-Disposition response header indicates in what form the response content should be displayed, whether in inline form (that is, a web page or part of a page), or downloaded and saved locally as an attachment.

Syntax:

js
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="filename.jpg"

Its first parameter can be inline or attachment.If the first parameter is inline (default value), it means to display it on the web page or as a web page.If the first parameter is an attachment indicating that it should be downloaded, most browsers will display a save as dialog box and will fill in the file name of the dialog box with filename if filename is provided.

Content-Disposition: attachment; filename="filename.jpg"

Content-Disposition can trigger the "Save As" dialog box

200 OK
Content-Type: text/html; charset=utf-8
Content-Disposition: attachment; filename="cool.html"
Content-Length: 22

<HTML>Save me!</HTML>

The above response will not be processed as simple html. Most browsers will attempt to save the file as cool.html

Send Content-Type:multipart/form-data through POST request in HTML page. Here is an example

js
POST /test.html HTTP/1.1
Host: example.org
Content-Type: multipart/form-data;boundary="boundary"

--boundary
Content-Disposition: form-data; name="field1"

value1
--boundary
Content-Disposition: form-data; name="field2"; filename="example.txt"

value2
--boundary--

Everyone still needs to know how to customize headers.

In essence, it is similar to the above Content-Disposition, except that we do not use the default header here. We customize a response header ourselves, decide the encoding method with the backend and return it. The frontend directly obtains this custom header, and then uses the corresponding decoding, such as using decodeURIComponent. Cross-origin scripts can read seven CORS-safelisted response headers by default: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified and Pragma. So if you want to get other headers, you need the backend to cooperate and set up

js
Access-Control-Expose-Headers: Content-Disposition, custom-header

In this way, the front end can obtain the corresponding exposed header field. It should be noted that Content-Disposition also needs to be exposed.

4️⃣ Base64 file download ​

javascript
const base64 = 'data:application/pdf;base64,...';
const a = document.createElement('a');
a.href = base64;
a.download = 'file.pdf';
a.click();

✅ Suitable for small files (such as exported pictures, QR codes, etc.).

5️⃣ Streaming download (back-end chunked output) ​

When used for large files or exporting reports (such as zip, excel):

  • The backend uses streams to chunk output
  • Frontend receives blob chunks in real time
  • Can be downloaded and saved at the same time

⚙️ Download optimization direction ​

Optimization pointsDescription
✅ Use Blob objectsConvenient binary handling; reading a complete large file still consumes memory
✅ Content-DispositionThe backend returns the file name
✅ Content-TypeEnsure the file MIME type is correct
✅ Asynchronous loading statusPrompt "File generating..."
✅ Chunked streaming downloadsPrevent blocking for large files

3. High-frequency interview questions and answers ​

Q1: How do you implement large file upload in your project? 👉 Sharding + parallel upload + breakpoint resume + file hash verification.

Q2: How to display the upload progress? 👉 Use xhr.upload.onprogress for uploads. Fetch has no standard upload progress event; reading its response stream measures download progress.

Q3: How to achieve instant transmission? 👉 Calculate the file hash before uploading, and the backend determines whether the file already exists.

Q4: How to prevent direct browser preview when downloading files? 👉 Add response headers to the backend:

js
res.setHeader('Content-Disposition', 'attachment; filename="xx.pdf"');

Q5: How to prevent security issues when uploading files? 👉 Front-end verification + back-end MIME checking + file isolation storage.

4. Summary of structured mind map ​

文件上传下载总结
├── 上传方式
│   ├── 表单上传(multipart/form-data)
│   ├── Ajax 上传(FormData)
│   ├── 云存储直传(带签名)
│   ├── 分片上传(chunk + merge)
│   ├── Base64 上传
│   └── 上传优化
│       ├── 秒传
│       ├── 并行上传
│       ├── 进度条
│       └── 重试机制
├── 下载方式
│   ├── 直接链接下载
│   ├── Blob 下载(fetch + blob)
│   ├── Base64 下载
│   ├── 流式下载(stream)
│   └── 下载优化
│       ├── 设置文件名
│       ├── 正确 Content-Type
│       ├── 防止预览
│       └── 大文件分块
└── 面试高频点
    ├── 分片上传原理
    ├── 秒传实现
    ├── 上传进度监听
    ├── Blob 下载实现
    └── Content-Disposition 作用

Reference connection:

https://juejin.cn/post/6895599073676492807

https://www.jianshu.com/p/7636d5c60a8d