Installation
First, add Axios to your project using npm:
npm install axios
Core Implementation
The following example demonstrates a complete resumable upload system with all essential features:
import axios from 'axios';
// Create a cancellation token source
const CancelToken = axios.CancelToken;
let cancelSource = null;
// Initialize chunk size (1MB default)
const DEFAULT_CHUNK_SIZE = 1024 * 1024;
// Continue an interrupted upload
const continueUpload = async (sourceFile, segmentSize, uploadId, uploadUrl) => {
if (!cancelSource) {
cancelSource = CancelToken.source();
}
// Calculate offset based on previously uploaded bytes
const offset = Math.min(segmentSize, sourceFile.size);
// Create Blob for current segment
const blobSlice = sourceFile.slice(0, offset);
// Prepare form data
const payload = new FormData();
payload.append('data', blobSlice);
payload.append('identifier', uploadId);
payload.append('offset', offset);
try {
const response = await axios.post(uploadUrl, payload, {
headers: { 'Content-Type': 'multipart/form-data' },
cancelToken: cancelSource.token,
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Upload progress: ${percentCompleted}%`);
}
});
return response.data;
} catch (error) {
if (axios.isCancel(error)) {
console.log('Upload cancelled');
}
throw error;
}
};
// Retry failed upload
const attemptUpload = async (sourceFile, segmentSize, uploadId, uploadUrl) => {
const maxRetries = 3;
let attempts = 0;
while (attempts < maxRetries) {
try {
return await continueUpload(sourceFile, segmentSize, uploadId, uploadUrl);
} catch (error) {
attempts++;
console.log(`Retry attempt ${attempts}/${maxRetries}`);
if (attempts >= maxRetries) {
throw new Error('Upload failed after multiple attempts');
}
}
}
};
// Pause ongoing upload
const pauseUpload = () => {
if (cancelSource) {
cancelSource.cancel('User requested pause');
cancelSource = null;
}
};
// Start new upload
const startUpload = async (sourceFile, segmentSize, uploadUrl) => {
if (!cancelSource) {
cancelSource = CancelToken.source();
}
// Generate unique identifier
const uniqueId = `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const payload = new FormData();
payload.append('data', sourceFile.slice(0, segmentSize));
payload.append('identifier', uniqueId);
payload.append('offset', 0);
payload.append('totalSize', sourceFile.size);
const response = await axios.post(uploadUrl, payload, {
headers: { 'Content-Type': 'multipart/form-data' },
cancelToken: cancelSource.token
});
return { uploadId: uniqueId, ...response.data };
};
// Progress tracking utility
const trackProgress = (progressEvent) => {
const calculation = (progressEvent.loaded / progressEvent.total) * 100;
return Math.round(calculation);
};
// Resumable upload handler
class ResumableUploadManager {
constructor(url, options = {}) {
this.uploadUrl = url;
this.segmentSize = options.segmentSize || DEFAULT_CHUNK_SIZE;
this.fileId = null;
}
async uploadFile(sourceFile, onProgress) {
// Start initial upload
const initialResult = await startUpload(sourceFile, this.segmentSize, this.uploadUrl);
this.fileId = initialResult.uploadId;
// Continue with remaining segments
let currentOffset = this.segmentSize;
while (currentOffset < sourceFile.size) {
const segment = sourceFile.slice(
currentOffset,
currentOffset + this.segmentSize
);
try {
await continueUpload(
segment,
this.segmentSize,
this.fileId,
this.uploadUrl
);
if (onProgress) {
const progress = trackProgress({
loaded: currentOffset,
total: sourceFile.size
});
onProgress(progress);
}
currentOffset += this.segmentSize;
} catch (error) {
await attemptUpload(
segment,
this.segmentSize,
this.fileId,
this.uploadUrl
);
}
}
return { success: true, fileId: this.fileId };
}
}
Usage Example
// DOM elements
const fileInput = document.getElementById('file-selector');
const progressBar = document.getElementById('upload-progress');
const pauseBtn = document.getElementById('pause-btn');
const resumeBtn = document.getElementById('resume-btn');
// Configuration
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
const API_ENDPOINT = 'https://api.example.com/files/upload';
// Initialize manager
const uploadManager = new ResumableUploadManager(API_ENDPOINT, {
segmentSize: CHUNK_SIZE
});
// Handle file selection
fileInput.addEventListener('change', async (event) => {
const selectedFile = event.target.files[0];
try {
const result = await uploadManager.uploadFile(selectedFile, (progress) => {
progressBar.style.width = `${progress}%`;
progressBar.textContent = `${progress}%`;
});
console.log('Upload complete:', result);
} catch (error) {
console.error('Upload failed:', error.message);
}
});
// Pause button handler
pauseBtn.addEventListener('click', () => {
pauseUpload();
});
// Resume button handler
resumeBtn.addEventListener('click', async () => {
const selectedFile = fileInput.files[0];
if (selectedFile && uploadManager.fileId) {
await uploadManager.uploadFile(selectedFile);
}
});
Key Features Breakdown
Resume Capability: The system maintains upload state using unique identifiers, allowing uploads to continue from the exacct byte position where they stopped.
Retry Logic: The attemptUpload function implements exponential backoff with configurable retry attempts for handling transient network failures.
Progress Tracking: Both onUploadProgress callback in Axios and custom progress calculation provide real-time upload status updates.
Cancellation Control: Using Axios CancelToken allows graceful pausing and cancellation of in-progress uploads without leaving dangling server connections.