Skip to content
Axios Advanced — Uploads, Caching, Testing & Production Patterns

Axios Advanced — Uploads, Caching, Testing & Production Patterns

DodaTech Updated Jun 6, 2026 17 min read

Axios goes beyond simple API calls — it handles file uploads with progress tracking, concurrent request management, caching, testing with mocks, and production-ready configuration for security and reliability.

What You’ll Learn

By the end of this tutorial, you’ll upload single and multiple files with real-time progress bars, download files as blobs with download progress, manage concurrent requests with Promise.all, implement caching strategies (in-memory, ETag, third-party), test Axios code with msw and axios-mock-adapter, and configure a production-ready Axios instance with CSRF protection, retries, and environment-specific settings.

Prerequisites: You should be comfortable with Axios interceptors and error handling. This tutorial builds on everything you’ve learned so far.

Why Advanced Patterns Matter

The basic Axios examples you’ve seen so far work for simple apps, but real applications need more:

  • File uploads — users need to upload profile pictures, PDFs, or CSV files, and they want to see progress
  • Concurrent requests — a dashboard needs to load users, posts, and comments simultaneously, not one after another
  • Cancellation — when the user types in a search box, you need to cancel the previous request to avoid race conditions
  • Caching — some data doesn’t change often; fetching it every time wastes bandwidth and slows the app
  • Testing — you can’t rely on a real server in tests; you need to mock responses
  • Production hardening — timeouts, retries, CSRF protection, and environment-specific config prevent real-world failures

Real-world use: Durga Antivirus Pro uses advanced Axios patterns for uploading suspicious files to its cloud scanning service — with per-file progress bars, automatic retry on network failure, cancellation when the user navigates away, and ETag-based caching for threat signature databases that update hourly.

Where This Fits in Your Learning Path

    flowchart LR
    A["Axios Getting Started"] --> B["Requests & Config"]
    B --> C["Interceptors & Error Handling"]
    C --> D["**Axios Advanced**"]
    D --> E["Full-Stack API Apps"]
    style D fill:#f97316,stroke:#c2410c,color:#fff
    style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
    style E fill:#22c55e,stroke:#16a34a,color:#22c55e
  

File Upload with Progress Tracking

Uploading files is different from sending JSON. You need to use FormData and set the content type to multipart/form-data.

Single File Upload with Progress Bar

async function uploadFile(file, onProgress) {
  const formData = new FormData()
  formData.append('file', file)       // The actual file
  formData.append('description', 'Uploaded file')

  const { data } = await axios.post('/upload', formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
    },
    onUploadProgress: (progressEvent) => {
      // Calculate percentage: (bytes sent / total bytes) * 100
      const percent = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total
      )
      onProgress?.(percent)
    },
  })

  return data
}

// Usage
const file = event.target.files[0]
uploadFile(file, (percent) => {
  console.log(`${percent}% uploaded`)
  // Update a progress bar: progressBar.style.width = percent + '%'
})

What’s happening step by step:

  1. FormData wraps the file into a format suitable for multipart uploads
  2. axios.post sends it with Content-Type: multipart/form-data
  3. onUploadProgress is called by the browser as chunks are sent
  4. progressEvent.loaded = bytes sent so far, progressEvent.total = total bytes
  5. The callback updates the UI with the percentage

Uploading Multiple Files

async function uploadMultiple(files, onProgress) {
  const formData = new FormData()
  files.forEach((file) => formData.append('files', file))

  const { data } = await axios.post('/upload-multiple', formData, {
    onUploadProgress: (e) => {
      const percent = Math.round((e.loaded * 100) / e.total)
      onProgress?.(percent)
    },
  })

  return data
}

Important: When uploading multiple files in one request, the progress shows the combined upload of all files, not per-file progress. For per-file progress bars, upload each file in a separate request.


File Download with Blob

Downloading files requires telling Axios to expect binary data (responseType: 'blob').

async function downloadFile(url, filename) {
  const response = await axios.get(url, {
    responseType: 'blob',
    onDownloadProgress: (progressEvent) => {
      const percent = Math.round(
        (progressEvent.loaded * 100) / progressEvent.total
      )
      console.log(`${percent}% downloaded`)
    },
  })

  // Create a temporary URL for the blob
  const blob = new Blob([response.data])
  const blobUrl = window.URL.createObjectURL(blob)

  // Trigger the browser's download dialog
  const link = document.createElement('a')
  link.href = blobUrl
  link.download = filename || 'download'
  document.body.appendChild(link)
  link.click()

  // Clean up
  document.body.removeChild(link)
  window.URL.revokeObjectURL(blobUrl)
}

// Usage
downloadFile('https://example.com/file.pdf', 'document.pdf')

Why responseType: 'blob'? Without it, Axios tries to parse the response as text. Binary data (PDFs, images, ZIPs) gets garbled into nonsense characters.

Why URL.revokeObjectURL? Blob URLs consume memory until the page is closed. Revoking them immediately after download prevents memory leaks.


Concurrent Requests

When your page needs data from multiple endpoints, don’t fetch them one after another — that wastes time. Fetch them simultaneously.

Sequential (Slow)

const users = await axios.get('/api/users')     // Wait 200ms
const posts = await axios.get('/api/posts')      // Wait 200ms
const comments = await axios.get('/api/comments') // Wait 200ms
// Total: 600ms

Concurrent (Fast)

async function fetchAll() {
  const [users, posts, comments] = await Promise.all([
    axios.get('/api/users'),
    axios.get('/api/posts'),
    axios.get('/api/comments'),
  ])

  return {
    users: users.data,
    posts: posts.data,
    comments: comments.data,
  }
}
// Total: ~200ms (all three run in parallel)

Promise.all sends all three requests at the same time and waits for all of them to complete. The total time is the time of the slowest request, not the sum.

Conditional Concurrency

Sometimes you don’t know in advance how many requests you’ll need:

async function fetchConditional(userId) {
  const requests = [axios.get(`/api/users/${userId}`)]

  // Only fetch posts if we have a valid user
  if (userId > 0) {
    requests.push(axios.get(`/api/users/${userId}/posts`))
  }

  const [user, posts] = await Promise.all(requests)
  return { user: user.data, posts: posts?.data }
}

Request Cancellation in Practice

You’ve seen cancellation basics. Here’s how it’s used in real scenarios.

Search Autocomplete

The classic pattern: every keystroke triggers a search request. If the user types faster than the API responds, cancel the previous request:

let controller

async function search(query) {
  // Cancel the previous in-flight request
  if (controller) controller.abort()

  // Create a new controller for this request
  controller = new AbortController()

  try {
    const { data } = await axios.get('/api/search', {
      params: { q: query },
      signal: controller.signal,
    })
    return data
  } catch (error) {
    // Ignore cancellation errors — they're expected
    if (!axios.isCancel(error)) throw error
  }
}

Why this matters: Without cancellation, if the user types “jav” then “javascript” quickly, both requests go out. If the “jav” response arrives after “javascript”, the results flicker and may show wrong data.

Cancelling on Page Navigation

const controller = new AbortController()

// Fetch data for the current page
axios.get('/api/dashboard-data', { signal: controller.signal })

// Cleanup when user leaves
window.addEventListener('beforeunload', () => {
  controller.abort()
})

Caching Strategies

Caching prevents fetching the same data twice, improving performance and reducing server load.

Simple In-Memory Cache

const cache = new Map()

async function fetchWithCache(url, ttl = 60000) {  // 60 second TTL
  const cached = cache.get(url)

  if (cached && Date.now() - cached.timestamp < ttl) {
    console.log('Cache hit:', url)
    return cached.data
  }

  console.log('Cache miss — fetching:', url)
  const { data } = await axios.get(url)
  cache.set(url, { data, timestamp: Date.now() })
  return data
}

ETag Caching

ETags are headers the server sends with responses. The browser sends the ETag back on subsequent requests, and the server replies with 304 Not Modified if nothing changed.

let etag = null
let cachedData = null

async function fetchWithEtag(url) {
  const headers = {}
  if (etag) headers['If-None-Match'] = etag  // Send stored ETag

  try {
    const response = await axios.get(url, { headers })

    if (response.status === 304) {
      // Server says "not modified" — use cached data
      console.log('Using cached data (304)')
      return cachedData
    }

    // Store the new ETag and data
    etag = response.headers.etag
    cachedData = response.data
    return response.data
  } catch (error) {
    // On error, return cached data if available
    return cachedData || null
  }
}

Third-Party: axios-cache-interceptor

npm install axios-cache-interceptor
import axios from 'axios'
import { setupCache } from 'axios-cache-interceptor'

const api = setupCache(axios.create({
  baseURL: '/api',
  timeout: 5000,
}))

// Automatically cached
const users = await api.get('/users')

// Force fresh request
const fresh = await api.get('/users', { cache: false })

Testing with Mocks

Never make real HTTP requests in tests. Mock the responses instead.

Using axios-mock-adapter

npm install axios-mock-adapter
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'

const mock = new MockAdapter(axios)

// Mock GET /api/users
mock.onGet('/api/users').reply(200, [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
])

// Mock POST with specific body
mock.onPost('/api/users', { name: 'Charlie' }).reply(201, {
  id: 3,
  name: 'Charlie',
})

// Mock errors
mock.onGet('/api/error').networkError()
mock.onGet('/api/timeout').timeout()

// Now test
const response = await axios.get('/api/users')
console.log(response.data) // [{ id: 1, name: 'Alice' }, ...]

Using msw (Mock Service Worker)

npm install msw --save-dev
import { http, HttpResponse } from 'msw'
import { setupServer } from 'msw/node'

const server = setupServer(
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ])
  })
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

test('fetches users', async () => {
  const { data } = await axios.get('/api/users')
  expect(data).toHaveLength(2)
})

msw intercepts at the network level, so it works with any HTTP client — not just Axios. This is the recommended approach for modern apps.


Production-Ready Configuration

A production Axios instance should handle timeouts, retries, auth tokens, CSRF protection, and error logging.

import axios from 'axios'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || '/api',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  withCredentials: true,        // Send cookies cross-origin
})

// Request interceptor: auth token + request tracking
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('authToken')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  config.headers['X-Request-Id'] = crypto.randomUUID()
  return config
})

// Response interceptor: error handling + token refresh
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      // Attempt token refresh
      try {
        const { data } = await axios.post('/auth/refresh', {
          refreshToken: localStorage.getItem('refreshToken'),
        })
        localStorage.setItem('authToken', data.accessToken)
        error.config.headers.Authorization = `Bearer ${data.accessToken}`
        return api(error.config)
      } catch {
        localStorage.clear()
        window.location.href = '/login'
      }
    }

    // Log to monitoring service
    console.error('[API Error]', {
      url: error.config?.url,
      status: error.response?.status,
      message: error.message,
    })

    return Promise.reject(error)
  }
)

export default api

CSRF Protection

Axios has built-in CSRF protection. It reads a token from a cookie and sends it as a header:

const api = axios.create({
  baseURL: '/api',
  xsrfCookieName: 'XSRF-TOKEN',         // Cookie name
  xsrfHeaderName: 'X-XSRF-TOKEN',       // Header name
  withCredentials: true,
})
// Axios automatically reads XSRF-TOKEN cookie and sets X-XSRF-TOKEN header

Environment-Specific Configuration

const config = {
  development: {
    baseURL: 'http://localhost:3000/api',
    timeout: 15000,
  },
  production: {
    baseURL: 'https://api.example.com',
    timeout: 10000,
  },
  test: {
    baseURL: 'https://test-api.example.com',
    timeout: 5000,
  },
}

const env = import.meta.env.MODE || 'development'
const api = axios.create(config[env])

Common Mistakes Beginners Make

1. Not Setting responseType: 'blob' for Downloads

// Wrong: garbled binary data
const res = await axios.get('/file.pdf')

// Correct: Axios treats response as binary
const res = await axios.get('/file.pdf', { responseType: 'blob' })

2. Cancelling the Wrong Controller in Search

// Wrong: creates new controller but doesn't track the old one
function search(query) {
  const controller = new AbortController() // New controller every call
  // ...
}

// Correct: store and abort the previous controller
let currentController
function search(query) {
  currentController?.abort()
  currentController = new AbortController()
  return axios.get('/search', { signal: currentController.signal })
}

3. Forgetting to Revoke Blob URLs

// Wrong: memory leak! The blob URL never gets freed
const url = URL.createObjectURL(blob)
link.href = url

// Correct: revoke after download completes
link.onclick = () => setTimeout(() => URL.revokeObjectURL(url), 10000)

4. Uploading Files Without FormData

// Wrong: sending raw file object as JSON
axios.post('/upload', file)

// Correct: wrap in FormData
const formData = new FormData()
formData.append('file', file)
axios.post('/upload', formData)

5. Fetching Sequentially When Concurrent Would Work

// Wrong: 3 sequential requests = 600ms
const users = await axios.get('/api/users')
const posts = await axios.get('/api/posts')
const comments = await axios.get('/api/comments')

// Correct: 3 concurrent requests = ~200ms
const [users, posts, comments] = await Promise.all([
  axios.get('/api/users'),
  axios.get('/api/posts'),
  axios.get('/api/comments'),
])

Practice Questions

  1. Why must you set responseType: 'blob' when downloading a file? Without it, Axios parses the response as text, corrupting binary data like PDFs or images.

  2. How does Promise.all improve performance over sequential await calls? It sends all requests simultaneously. The total time equals the slowest request, not the sum of all requests.

  3. Why do you need to track the active AbortController in a search autocomplete? To cancel the previous request when the user types a new character, preventing race conditions where old results overwrite new ones.

  4. What is the difference between axios-mock-adapter and msw? axios-mock-adapter intercepts Axios-specific requests. msw intercepts at the network level and works with any HTTP client.

  5. What does URL.revokeObjectURL do and why is it important? It frees memory associated with a blob URL. Without it, blob URLs accumulate until the page is closed, causing memory leaks.

Challenge

Build a file management dashboard that:

  • Accepts drag-and-drop file uploads with per-file progress bars
  • Uploads files concurrently (max 3 at a time)
  • Allows cancelling individual uploads or all at once
  • Shows upload speed (bytes/second) for each file
  • Lets users preview uploaded images in a new tab
  • Displays stats: total files, completed, failed, and average speed

FAQ

How do I upload multiple files with per-file progress?

Upload each file in a separate request with its own onUploadProgress callback. One FormData with multiple files only gives you combined progress.

Can Axios be used in Web Workers?

Yes. Axios works in dedicated and shared web workers through the standard XHR API available in workers.

How do I handle large file uploads (500MB+)?

Consider chunked uploads — split the file into parts (e.g., 5MB each), upload each chunk with a progress callback, and reassemble on the server. Libraries like tus-js-client specialize in resumable large file uploads.

Is axios-cache-interceptor production-ready?

Yes. It supports ETag, TTL, storage adapters (memory, localStorage, sessionStorage), and manual cache invalidation.

How do I test rate limiting with mocks?

Use mock.onGet('/api/endpoint').reply(429, { message: 'Too Many Requests' }, { 'Retry-After': '30' }) to simulate rate limit responses and test your app’s handling.

Try It Yourself: File Upload Dashboard

This interactive dashboard demonstrates all the advanced patterns you’ve learned — drag-and-drop upload, per-file progress, cancellation, and stats.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Axios File Upload Dashboard</title>
  <script src="https://cdn.jsdelivr.net/npm/axios@1.7.0/dist/axios.min.js"></script>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; color: #e2e8f0; padding: 2rem; }
    .container { max-width: 1000px; margin: 0 auto; }
    h1 { font-size: 1.75rem; }
    .subtitle { color: #94a3b8; margin-bottom: 1.5rem; }
    .card { background: #1e293b; border-radius: 0.75rem; padding: 1.5rem; border: 1px solid #334155; margin-bottom: 1rem; }
    .card h2 { font-size: 1.05rem; margin-bottom: 0.75rem; color: #38bdf8; }
    .dropzone { border: 2px dashed #334155; border-radius: 0.75rem; padding: 3rem 2rem; text-align: center; cursor: pointer; transition: all 0.2s; }
    .dropzone:hover, .dropzone.dragover { border-color: #38bdf8; background: rgba(56, 189, 248, 0.05); }
    .dropzone.dragover { border-color: #22c55e; background: rgba(34, 197, 94, 0.05); }
    .dropzone p { color: #94a3b8; font-size: 0.95rem; }
    .dropzone .icon { font-size: 2.5rem; margin-bottom: 0.5rem; }
    .flex { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
    .btn { background: #38bdf8; color: #0f172a; border: none; padding: 0.5rem 1.25rem; border-radius: 0.375rem; font-weight: 600; cursor: pointer; font-size: 0.875rem; }
    .btn:hover { background: #7dd3fc; }
    .btn-outline { background: transparent; color: #38bdf8; border: 1px solid #38bdf8; }
    .btn-outline:hover { background: #38bdf8; color: #0f172a; }
    .btn-danger { background: #ef4444; color: #fff; }
    .btn-danger:hover { background: #dc2626; }
    .btn-sm { padding: 0.25rem 0.5rem; font-size: 0.75rem; }
    .btn-success { background: #22c55e; color: #0f172a; }
    .file-item { background: #0f172a; border: 1px solid #334155; border-radius: 0.5rem; padding: 0.75rem; margin-bottom: 0.5rem; }
    .file-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.375rem; }
    .file-name { font-weight: 600; font-size: 0.9rem; }
    .file-size { color: #94a3b8; font-size: 0.75rem; }
    .file-status { font-size: 0.75rem; padding: 0.125rem 0.375rem; border-radius: 0.25rem; }
    .file-status.pending { background: #334155; color: #94a3b8; }
    .file-status.uploading { background: #f59e0b; color: #0f172a; }
    .file-status.done { background: #22c55e; color: #0f172a; }
    .file-status.error { background: #ef4444; color: #fff; }
    .file-status.cancelled { background: #475569; color: #94a3b8; }
    .progress-bar { background: #1e293b; border-radius: 999px; height: 6px; margin-top: 0.375rem; overflow: hidden; }
    .progress-fill { height: 100%; border-radius: 999px; transition: width 0.2s; width: 0%; }
    .progress-fill.uploading { background: linear-gradient(90deg, #38bdf8, #22c55e); }
    .progress-fill.done { background: #22c55e; }
    .progress-fill.error { background: #ef4444; }
    .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; margin-top: 1rem; }
    .stat-card { background: #0f172a; padding: 0.75rem; border-radius: 0.5rem; text-align: center; }
    .stat-value { font-size: 1.5rem; font-weight: 700; color: #38bdf8; }
    .stat-label { font-size: 0.75rem; color: #94a3b8; }
    .file-list { max-height: 400px; overflow-y: auto; }
    .empty-state { color: #475569; text-align: center; padding: 2rem; font-size: 0.9rem; }
    @media (max-width: 600px) { .stats { grid-template-columns: repeat(2, 1fr); } }
  </style>
</head>
<body>
<div class="container">
  <h1>File Upload Dashboard</h1>
  <p class="subtitle">Drag and drop files, track progress, cancel uploads</p>

  <div class="card">
    <div class="dropzone" id="dropzone">
      <div class="icon">&#128230;</div>
      <p>Drag and drop files here, or <strong style="color:#38bdf8;">click to browse</strong></p>
      <p style="font-size:0.8rem;margin-top:0.5rem;">Any file type accepted</p>
    </div>
    <input type="file" id="fileInput" multiple style="display:none;" />
    <div class="flex" style="margin-top:1rem;">
      <button class="btn btn-success btn-sm" onclick="uploadAll()">Upload All</button>
      <button class="btn btn-outline btn-sm" onclick="cancelAll()">Cancel All</button>
      <button class="btn btn-danger btn-sm" onclick="clearCompleted()">Clear Completed</button>
    </div>
  </div>

  <div class="card">
    <h2>Files</h2>
    <div class="file-list" id="fileList">
      <div class="empty-state">No files added yet. Drop some above.</div>
    </div>
    <div class="stats">
      <div class="stat-card"><div class="stat-value" id="totalStat">0</div><div class="stat-label">Total</div></div>
      <div class="stat-card"><div class="stat-value" id="uploadedStat">0</div><div class="stat-label">Uploaded</div></div>
      <div class="stat-card"><div class="stat-value" id="failedStat">0</div><div class="stat-label">Failed</div></div>
      <div class="stat-card"><div class="stat-value" id="speedStat">0 B/s</div><div class="stat-label">Speed</div></div>
    </div>
  </div>
</div>

<script>
const files = new Map();
let fileIdCounter = 0;
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');

dropzone.addEventListener('click', () => fileInput.click());
dropzone.addEventListener('dragover', (e) => { e.preventDefault(); dropzone.classList.add('dragover'); });
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragover'));
dropzone.addEventListener('drop', (e) => {
  e.preventDefault();
  dropzone.classList.remove('dragover');
  handleFiles(Array.from(e.dataTransfer.files));
});
fileInput.addEventListener('change', () => {
  handleFiles(Array.from(fileInput.files));
  fileInput.value = '';
});

function handleFiles(newFiles) {
  newFiles.forEach(file => {
    const id = ++fileIdCounter;
    files.set(id, { id, file, name: file.name, size: file.size, status: 'pending', progress: 0, controller: null, speed: 0 });
  });
  renderFileList();
  updateStats();
}

function renderFileList() {
  const container = document.getElementById('fileList');
  if (files.size === 0) {
    container.innerHTML = '<div class="empty-state">No files added yet. Drop some above.</div>';
    return;
  }
  container.innerHTML = Array.from(files.values()).map(f => {
    const sizeStr = f.size > 1024 * 1024 ? (f.size / (1024 * 1024)).toFixed(1) + ' MB' : (f.size / 1024).toFixed(1) + ' KB';
    const statusClass = f.status === 'uploading' ? 'uploading' : f.status;
    return `
      <div class="file-item">
        <div class="file-header">
          <div><span class="file-name">${f.name}</span> <span class="file-size">${sizeStr}</span></div>
          <div class="flex">
            <span class="file-status ${statusClass}">${f.status}</span>
            ${f.status === 'pending' ? `<button class="btn btn-sm btn-success" onclick="uploadFile(${f.id})">Upload</button>` : ''}
            ${f.status === 'uploading' ? `<button class="btn btn-sm btn-danger" onclick="cancelFile(${f.id})">Cancel</button>` : ''}
            ${f.status === 'done' ? `<button class="btn btn-sm btn-outline" onclick="previewFile(${f.id})">Preview</button>` : ''}
            ${['done', 'error', 'cancelled'].includes(f.status) ? `<button class="btn btn-sm btn-outline" onclick="removeFile(${f.id})">Remove</button>` : ''}
          </div>
        </div>
        <div class="progress-bar"><div class="progress-fill ${statusClass}" style="width:${f.status === 'done' ? 100 : f.progress}%"></div></div>
        ${f.status === 'uploading' ? `<div style="display:flex;justify-content:space-between;font-size:0.7rem;color:#94a3b8;margin-top:0.25rem;"><span>${f.progress}%</span><span>${f.speed || 0} B/s</span></div>` : ''}
      </div>
    `;
  }).join('');
}

function uploadAll() { files.forEach((f, id) => { if (f.status === 'pending') uploadFile(id); }); }
function cancelAll() { files.forEach((f, id) => { if (f.status === 'uploading') cancelFile(id); }); }
function clearCompleted() { files.forEach((f, id) => { if (['done', 'error', 'cancelled'].includes(f.status)) files.delete(id); }); renderFileList(); updateStats(); }

async function uploadFile(id) {
  const f = files.get(id);
  if (!f || f.status !== 'pending') return;
  f.status = 'uploading';
  f.progress = 0;
  f.controller = new AbortController();
  renderFileList();
  const formData = new FormData();
  formData.append('file', f.file);
  let lastLoaded = 0, lastTime = Date.now();
  try {
    const response = await axios.post('https://httpbin.org/post', formData, {
      signal: f.controller.signal,
      onUploadProgress: (e) => {
        f.progress = Math.round((e.loaded * 100) / e.total);
        const now = Date.now();
        const elapsed = (now - lastTime) / 1000;
        if (elapsed > 0) f.speed = Math.round((e.loaded - lastLoaded) / elapsed);
        lastLoaded = e.loaded; lastTime = now;
        renderFileList();
      },
    });
    f.status = 'done'; f.progress = 100; f.responseData = response.data;
  } catch (err) {
    f.status = axios.isCancel(err) ? 'cancelled' : 'error';
    if (!axios.isCancel(err)) f.errorMessage = err.message;
  }
  renderFileList(); updateStats();
}

function cancelFile(id) { const f = files.get(id); if (f && f.controller) { f.controller.abort(); f.status = 'cancelled'; renderFileList(); updateStats(); } }
function removeFile(id) { files.delete(id); renderFileList(); updateStats(); }
function previewFile(id) { const f = files.get(id); if (!f || !f.responseData) return; const url = URL.createObjectURL(new Blob([f.file])); window.open(url, '_blank'); setTimeout(() => URL.revokeObjectURL(url), 60000); }
function updateStats() { let total = files.size, uploaded = 0, failed = 0; files.forEach(f => { if (f.status === 'done') uploaded++; if (f.status === 'error') failed++; }); document.getElementById('totalStat').textContent = total; document.getElementById('uploadedStat').textContent = uploaded; document.getElementById('failedStat').textContent = failed; }
</script>
</body>
</html>

Key Features

  • Drag and drop — intuitive file selection via drag/drop or click-to-browse
  • Per-file progress bars — visual progress with percentage and speed
  • Concurrent uploads — upload all files simultaneously with individual cancellation
  • Cancel individual or all — cancel any in-flight upload with AbortController
  • Download preview — open completed files in a new tab via blob URL
  • Stats dashboard — total / uploaded / failed counts

What’s Next

You’ve completed the Axios series. Next, explore other frontend libraries:

LibraryWhat You’ll Learn
Lodash Getting StartedUtility functions for arrays, objects, and data manipulation
Chart.jsCreate interactive charts and graphs

Related topics: JavaScript Promises, File API, REST APIs.

What’s Next

Congratulations on completing this Axios Advanced tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro