← Назад к вопросам

Как загрузить файл частями?

1.3 Junior🔥 151 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Загрузка файлов частями (Chunked Upload)

Зачем нужна загрузка по частям

Chunked Upload — это метод разделения большого файла на меньшие части (chunks) для:

  • Обработки больших файлов, которые не помещаются в память
  • Возможности возобновления загрузки при разрыве соединения
  • Показа прогресса загрузки
  • Уменьшения нагрузки на сервер

1. Базовый алгоритм на клиенте

const CHUNK_SIZE = 1024 * 1024; // 1 MB

function uploadFileInChunks(file) {
  const chunks = [];
  let offset = 0;
  
  while (offset < file.size) {
    const chunk = file.slice(offset, offset + CHUNK_SIZE);
    chunks.push(chunk);
    offset += CHUNK_SIZE;
  }
  
  return chunks;
}

const file = document.querySelector("input[type=file]").files[0];
const chunks = uploadFileInChunks(file);

2. Загрузка с прогрессом

async function uploadWithProgress(file, onProgress) {
  const CHUNK_SIZE = 1024 * 1024;
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  let uploadedBytes = 0;
  
  for (let i = 0; i < totalChunks; i++) {
    const start = i * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);
    
    const formData = new FormData();
    formData.append("chunk", chunk);
    formData.append("chunkIndex", i);
    formData.append("totalChunks", totalChunks);
    formData.append("fileName", file.name);
    
    try {
      const response = await fetch("/api/upload-chunk", {
        method: "POST",
        body: formData
      });
      
      if (!response.ok) throw new Error("Ошибка загрузки");
      
      uploadedBytes += chunk.size;
      const progress = (uploadedBytes / file.size) * 100;
      onProgress(progress);
      
    } catch (error) {
      console.error(`Ошибка загрузки части ${i}:`, error);
    }
  }
  
  return uploadedBytes === file.size;
}

3. С возможностью возобновления (Resume)

class FileUploader {
  constructor(file, chunkSize = 1024 * 1024) {
    this.file = file;
    this.chunkSize = chunkSize;
    this.uploadId = Date.now();
    this.uploadedChunks = new Set();
  }
  
  async upload(onProgress) {
    const totalChunks = Math.ceil(this.file.size / this.chunkSize);
    
    const status = await this.getUploadStatus();
    this.uploadedChunks = new Set(status.uploadedChunks);
    
    for (let i = 0; i < totalChunks; i++) {
      if (this.uploadedChunks.has(i)) continue;
      
      const chunk = this.file.slice(
        i * this.chunkSize,
        (i + 1) * this.chunkSize
      );
      
      await this.uploadChunk(i, chunk, totalChunks);
      this.uploadedChunks.add(i);
      
      const progress = (this.uploadedChunks.size / totalChunks) * 100;
      onProgress(progress);
    }
    
    await this.completeUpload();
  }
  
  async uploadChunk(index, chunk, totalChunks) {
    const formData = new FormData();
    formData.append("chunk", chunk);
    formData.append("chunkIndex", index);
    formData.append("totalChunks", totalChunks);
    formData.append("uploadId", this.uploadId);
    formData.append("fileName", this.file.name);
    
    const response = await fetch("/api/upload-chunk", {
      method: "POST",
      body: formData
    });
    
    if (!response.ok) throw new Error(`Ошибка загрузки части ${index}`);
    return response.json();
  }
  
  async getUploadStatus() {
    const response = await fetch(
      `/api/upload-status?uploadId=${this.uploadId}`
    );
    return response.json();
  }
  
  async completeUpload() {
    const response = await fetch("/api/upload-complete", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        uploadId: this.uploadId,
        fileName: this.file.name
      })
    });
    return response.json();
  }
}

4. С параллельной загрузкой

async function uploadInParallel(file, parallelCount = 3) {
  const CHUNK_SIZE = 1024 * 1024;
  const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
  const uploadedChunks = new Array(totalChunks).fill(false);
  
  async function uploadChunk(index) {
    const start = index * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, file.size);
    const chunk = file.slice(start, end);
    
    const formData = new FormData();
    formData.append("chunk", chunk);
    formData.append("chunkIndex", index);
    formData.append("totalChunks", totalChunks);
    
    await fetch("/api/upload-chunk", {
      method: "POST",
      body: formData
    });
    
    uploadedChunks[index] = true;
    const progress = (uploadedChunks.filter(Boolean).length / totalChunks) * 100;
    console.log(`Прогресс: ${progress.toFixed(2)}%`);
  }
  
  const queue = Array.from({ length: totalChunks }, (_, i) => i);
  const active = [];
  
  while (queue.length > 0 || active.length > 0) {
    while (active.length < parallelCount && queue.length > 0) {
      const chunkIndex = queue.shift();
      const promise = uploadChunk(chunkIndex)
        .then(() => {
          active.splice(active.indexOf(promise), 1);
        });
      active.push(promise);
    }
    
    if (active.length > 0) {
      await Promise.race(active);
    }
  }
}

5. Вычисление контрольной суммы

async function calculateFileHash(file) {
  const buffer = await file.arrayBuffer();
  const hashBuffer = await crypto.subtle.digest("SHA-256", buffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
}

async function uploadWithHash(file) {
  const hash = await calculateFileHash(file);
  
  const formData = new FormData();
  formData.append("file", file);
  formData.append("hash", hash);
  
  await fetch("/api/upload", {
    method: "POST",
    body: formData
  });
}

Итоги

  • Разбивай файл на chunk'и (обычно 1-5 MB)
  • Отправляй каждый chunk отдельным запросом
  • Показывай прогресс пользователю
  • Реализуй возобновление при разрыве соединения
  • Используй параллельную загрузку для скорости
  • Проверяй целостность файла (hash)
  • На сервере объединяй части в полный файл
Как загрузить файл частями? | PrepBro