Гид по технологиям

Скачивание файлов в Node.js — методы и обработка ошибок

5 min read Разработка Обновлено 03 Jan 2026
Скачивание файлов в Node.js — методы и обработка ошибок
Скачивание файлов в Node.js — методы и обработка ошибок

Две стопки файлов и документов рядом

Почему стоит скачивать файлы локально

Скачивание файлов в локальное хранилище даёт несколько практических преимуществ: быстрый доступ без интернета, полный контроль и владение данными, возможность дальнейшей обработки (индексация, бэкап, трансформация). Этот подход полезен для офлайн-работ, бэкап-утилит, миграций и ETL-процессов.

Ключевые термины

  • Stream: поток данных, удобный для записи/чтения больших файлов без загрузки всего в память.
  • Callback/Promise: способы обработки асинхронных операций в Node.js.

Скачивание без сторонних библиотек

Для простого и контролируемого скачивания можно использовать три встроенных модуля: fs, https, path. Модуль fs отвечает за запись файлов, https — за выполнение HTTPS-запросов, path — за работу с путями и извлечение имени файла.

Импорт необходимых модулей (пример):

const https = require('https');
const fs = require('fs');
const path = require('path');

Простой пример из исходного описания (переведён):

const filename = path.basename(url);

https.get(url, (res) => {
})

const fileStream = fs.createWriteStream(filename);
res.pipe(fileStream);

fileStream.on('finish', () => {
  fileStream.close();
  console.log('Download finished');
});

Этот код рабочий для базовых случаев, но в реальных приложениях важно учитывать ошибки потоков, проверку HTTP-кода ответа, редиректы и ограничения по таймаутам.

Надёжная функция скачивания (советированная версия)

Ниже — улучшенный, промис-ориентированный вариант функции скачивания, учитывающий проверку статуса ответа и обработку ошибок потоков. Он работает для HTTPS и HTTP (понадобится модуль http для plain-HTTP):

const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');
const { URL } = require('url');

function downloadFile(urlString, destFolder = '.') {
  return new Promise((resolve, reject) => {
    try {
      const url = new URL(urlString);
      const protocol = url.protocol === 'https:' ? https : http;
      const filename = path.basename(url.pathname) || 'download';
      const destination = path.join(destFolder, filename);

      const request = protocol.get(url, (res) => {
        // Обработка HTTP-редиректа (302/301)
        if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
          // рекурсивно следуем редиректу
          resolve(downloadFile(res.headers.location, destFolder));
          return;
        }

        if (res.statusCode < 200 || res.statusCode >= 300) {
          reject(new Error(`HTTP error: ${res.statusCode} ${res.statusMessage}`));
          res.resume(); // очистить поток
          return;
        }

        const fileStream = fs.createWriteStream(destination);
        res.pipe(fileStream);

        fileStream.on('finish', () => {
          fileStream.close(() => resolve(destination));
        });

        fileStream.on('error', (err) => {
          // удалить частично записанный файл
          fileStream.close(() => {
            fs.unlink(destination, () => reject(err));
          });
        });
      });

      request.on('error', (err) => reject(err));
      request.setTimeout(30000, () => {
        request.abort();
        reject(new Error('Request timed out'));
      });
    } catch (err) {
      reject(err);
    }
  });
}

Пояснения к реализации:

  • Использование URL позволяет корректно извлечь pathname и имя файла. path.basename предотвращает запись в произвольные поддиректории, но всё равно нужно валидировать результат.
  • Проверяем статусы 300–399 для редиректов, 200–299 как признак успеха.
  • Обработка ошибок потоков и удаление частично записанного файла повышают надёжность.
  • Таймаут защищает от зависания запроса.

Запуск функции и скачивание нескольких файлов

Если вы передаете URL-ы через командную строку, используйте process.argv:

const args = process.argv;
const urls = args.slice(2);

Promise.all(urls.map(u => downloadFile(u, './files')))
  .then(results => console.log('Downloaded:', results))
  .catch(err => console.error('Error downloading files:', err));

Запуск из терминала:

node script.js https://example.com/a.jpg https://example.com/b.pdf

Обработка ошибок при скачивании

Общие ошибки при скачивании: сетевые сбои, неверный URL, ошибка записи на диск, недоступный ресурс, редиректы, отсутствие прав на запись, таймауты.

Советы по обработке ошибок:

  • Не полагайтесь только на try/catch для асинхронного кода; используйте промисы и обработчики событий.
  • Всегда слушайте ‘error’ у потоков (stream.on(‘error’, …)).
  • Проверяйте res.statusCode и реагируйте на коды 4xx/5xx.
  • Обрабатывайте редиректы (3xx) и потенциально ограничивайте глубину редиректов.
  • Очищайте частично записанные файлы при ошибках.
  • Добавьте таймауты и ограничьте число попыток при повторных ошибках.

Пример простого механизма повторных попыток (экспоненциальная задержка)

async function downloadWithRetries(url, dest, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await downloadFile(url, dest);
    } catch (err) {
      if (attempt === maxRetries) throw err;
      const delay = Math.pow(2, attempt) * 500; // 500ms, 1000ms, 2000ms...
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

Try/catch и асинхронность

Важно: try/catch ловит синхронные ошибки и ошибки await-операторов. Если функция возвращает промис и вы не используете await, то try/catch не поймает ошибку — используйте .catch или await внутри async-функций.

Пример неправильного использования (не уловит асинхронную ошибку):

try {
  downloadFile(url); // возвращает промис
} catch (error) {
  console.log(error); // не сработает для ошибки внутри промиса
}

Правильный вариант:

(async () => {
  try {
    await downloadFile(url);
  } catch (err) {
    console.error('Download failed', err);
  }
})();

Использование сторонних библиотек

Иногда проще и безопаснее использовать готовые пакеты, которые уже решают вопросы редиректов, таймаутов и параллелизации.

Пример с пакетом download (npm):

npm install download

const download = require('download');
const args = process.argv;
const urls = args.slice(2);

async function downloadFile(urls){
  await Promise.all(urls.map((url) => download(url, "files")));
}

downloadFile(urls);

Альтернативы: axios, got, node-fetch. Многие из них поддерживают промежуточную обработку потоков и дополнительные опции (таймаут, заголовки, авторизация). Выбор зависит от требований: нужна ли поддержка HTTP/2, прокси, retry-логика, контроль над потоками.

Когда встроенный подход не подходит

  • Если нужен сложный контроль параллелизма с очередями и приоритетами — лучше взять библиотеку задач/очередей.
  • Если требуется авторизация (OAuth, токены) и сложная логика повторных попыток — часто удобнее использовать axios/got.
  • Для загрузки очень больших файлов (десятки ГБ) следует учитывать ограничения файловой системы и обеспечить контроль использования диска и памяти.

Безопасность и конфиденциальность

  • Проверяйте URL-ы от внешних источников: не позволяйте записывать файлы с опасными именами или в системные папки.
  • Обрабатывайте и нормализуйте имена файлов (например, удалять ../ и запрещённые символы).
  • Не выполняйте загруженные файлы без дополнительной проверки и валидации.
  • Если загружаете конфиденциальные данные, храните их шифрованными и следите за правами доступа.
  • При работе с пользовательскими URL-ами учитывайте риск SSRF и ограничивайте набор разрешённых доменов.

Рольовые чек-листы перед релизом

Разработчик:

  • Написал обработку ошибок для потоков.
  • Добавил проверку HTTP-кодов.
  • Реализовал таймаут и retry стратегию.

DevOps:

  • Убедился, что у сервиса есть место на диске и соответствующие права.
  • Настроил мониторинг дискового пространства и ошибок записи.

QA:

  • Протестировал скачивание больших и маленьких файлов.
  • Смоделировал сетевые сбои и проверил поведение повторных попыток.

Критерии приёмки

  • Файлы корректно скачиваются и сохраняются в указанную папку.
  • При ошибке записываются понятные сообщения и частично записанные файлы удаляются.
  • Таймауты работают, и поток не висит бесконечно.
  • При передаче нескольких URL-ов все файлы корректно скачиваются параллельно или последовательно в зависимости от настроек.

Факты и подсказки

  • HTTP-успех: коды 200–299.
  • Редиректы: коды 300–399 — нужно следовать или отбросить в зависимости от политики.
  • Всегда слушайте события ‘error’ у потоков.

Совместимость и миграция

Код с использованием потоков fs и модулей http/https работает в современных версиях Node.js. При миграции убедитесь, что версия Node.js поддерживает синтаксис async/await и URL API (Node.js 8+), и при необходимости обновите зависимые пакеты.

Примеры типичных ошибок и их решения

  • Файл не скачивается, но нет ошибки: проверьте res.statusCode и обработчик res.resume() при ошибках.
  • Частично скачанный файл остаётся на диске: добавьте слушатель fileStream.on(‘error’) и fs.unlink для удаления.
  • Неправильное имя файла с query-параметрами: используйте path.basename(new URL(url).pathname).

Краткое резюме

Скачивание файлов в Node.js легко организовать с помощью встроенных модулей, но для продакшн-решений важно добавить обработку ошибок, таймауты, поддержку редиректов и валидацию имён файлов. При сложной логике загрузок задумайтесь о готовых библиотеках, которые упрощают retry-политику и параллелизм.

Важно

Перед тем как скачивать файлы от незнакомых источников, всегда проверяйте URL, фильтруйте домены и не выполняйте загруженное содержимое автоматически.

Конец статьи

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Изменить ключевой кадр Live Photo на iPhone
Фото

Изменить ключевой кадр Live Photo на iPhone

Как обойти заблокированные сайты — PHProxy
Сеть

Как обойти заблокированные сайты — PHProxy

6 полезных, но малоизвестных функций macOS
Советы

6 полезных, но малоизвестных функций macOS

Оглавление в Google Документах: как использовать
Инструменты

Оглавление в Google Документах: как использовать

Настройка IKEA DIRIGERA Smart Hub
Умный дом

Настройка IKEA DIRIGERA Smart Hub

Как справляться со стрессом при удалённой работе
Удалённая работа

Как справляться со стрессом при удалённой работе