You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Node/Electron环境实现Axios断点续传文件下载

Implementing Pause/Resume for File Downloads in Node/Electron

Great work getting the progress tracking up and running! Adding pause/resume functionality relies on HTTP Range requests (to fetch only the remaining bytes) and careful management of your download stream and request state. Let's adapt your existing code to support these features.

Key Concepts to Understand

  • HTTP Range Headers: When resuming, we tell the server to send only the bytes we haven't downloaded yet using Range: bytes=X- where X is the number of bytes already received.
  • AbortController: Used to cancel the ongoing HTTP request when pausing (prevents unnecessary data from being downloaded).
  • File Append Mode: Instead of overwriting the file each time, we'll open it in append mode (flags: 'a') to add new bytes to the existing file.
  • State Tracking: We need to keep track of downloaded bytes, the active stream, and the abort controller to manage pause/resume actions.

Modified Code with Pause/Resume Support

Here's an updated version of your code with all the required features. I've added comments to explain each new part:

const Fs = require('fs');
const Path = require('path');
const Axios = require('axios');

// Track download state
let startTime, endTime;
let receivedBytes = 0;
let writer = null;
let abortController = null;
let downloadUrl = '';
const outputPath = Path.resolve(__dirname, 'images', 'axios.jpg');

function startTimer() { startTime = new Date(); }
function endTimer() { 
  endTime = new Date(); 
  const timeDiff = (endTime - startTime) / 1000; 
  console.log(`Download completed in ${timeDiff} seconds`); 
}

// Check if a partial download exists (for resume on app restart)
function getExistingFileSize() {
  try {
    return Fs.statSync(outputPath).size;
  } catch (err) {
    // File doesn't exist yet
    return 0;
  }
}

async function downloadFile(url) {
  downloadUrl = url;
  // Load existing downloaded bytes if file exists
  receivedBytes = getExistingFileSize();
  
  startTimer();
  console.log(receivedBytes > 0 ? 'Resuming download…' : 'Starting download…');

  abortController = new AbortController();
  const signal = abortController.signal;

  try {
    const { data, headers } = await Axios({
      url,
      method: 'GET',
      responseType: 'stream',
      signal,
      // Send Range header if we're resuming
      headers: receivedBytes > 0 ? { Range: `bytes=${receivedBytes}-` } : {}
    });

    const totalLength = receivedBytes + parseInt(headers['content-length']);
    
    // Open file in append mode (create if it doesn't exist)
    writer = Fs.createWriteStream(outputPath, { flags: 'a' });
    data.pipe(writer);

    data.on('data', (chunk) => {
      receivedBytes += chunk.length;
      showProgress(receivedBytes, totalLength);
    });

    data.on('end', () => {
      endTimer();
      console.log('Download finished!');
      // Reset state after completion
      resetState();
    });

    data.on('error', (error) => {
      if (error.name === 'AbortError') {
        console.log('Download paused');
      } else {
        console.log('Download error:', error);
        resetState();
      }
    });

    writer.on('error', (error) => {
      console.log('File write error:', error);
      abortController.abort();
      resetState();
    });
  } catch (err) {
    if (err.name !== 'AbortError') {
      console.log('Request failed:', err);
      resetState();
    }
  }
}

function pauseDownload() {
  if (abortController) {
    abortController.abort();
    if (writer) {
      writer.close(); // Close the file stream to prevent corruption
    }
  }
}

function resumeDownload() {
  if (downloadUrl) {
    downloadFile(downloadUrl);
  }
}

function showProgress(received, total) {
  const percentage = ((received / total) * 100).toFixed(2);
  console.log(`${percentage}% | ${received} bytes out of ${total} bytes`);
}

function resetState() {
  abortController = null;
  writer = null;
}

// Example usage:
downloadFile('https://unsplash.com/photos/AaEQmoufHLk/download?force=true');

// To pause after 5 seconds (for testing):
// setTimeout(pauseDownload, 5000);
// To resume after another 5 seconds:
// setTimeout(resumeDownload, 10000);

How It Works

  1. State Tracking: We maintain variables for receivedBytes, abortController, and writer to manage the current download state.
  2. Resume on Startup: The getExistingFileSize function checks if a partial file exists, so we can pick up where we left off even if the app restarts.
  3. Pause: Calling pauseDownload() uses AbortController to cancel the HTTP request and closes the file stream to prevent corruption.
  4. Resume: resumeDownload() re-calls downloadFile, which sends a Range header to fetch only the remaining bytes and appends them to the existing file.
  5. Error Handling: We differentiate between intentional aborts (pauses) and other errors to keep the state clean.

Notes for Electron

  • If you're building a UI, you can bind pauseDownload() and resumeDownload() to button click events directly.
  • For better persistence across app restarts, save the receivedBytes value to a config file (e.g., using electron-store) so you can resume downloads even after the app is closed and reopened.
  • Verify the server supports Range requests first (most modern servers do, but you can check with a HEAD request to confirm the Accept-Ranges header is present).

内容的提问来源于stack exchange,提问作者Cristian Muscalu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 21:57:37