← Назад к вопросам
Как загрузить файл частями?
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)
- На сервере объединяй части в полный файл