Как скачать файл с помощью Node.js (без использования сторонних библиотек)?
Как скачать файл с помощью Node.js, не используя сторонние библиотеки?
Мне не нужно ничего специфичного. Я просто хочу скачать файл по указанному URL и сохранить его в заданной директории.
5 ответ(ов)
Не забудьте обрабатывать ошибки! Следующий код основан на ответе Агусто Романа.
var http = require('http');
var fs = require('fs');
var download = function(url, dest, cb) {
var file = fs.createWriteStream(dest);
var request = http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(cb); // close() - асинхронная функция, вызываем cb после завершения close.
});
}).on('error', function(err) { // Обработка ошибок
fs.unlink(dest); // Удаляем файл асинхронно. (Но не проверяем результат)
if (cb) cb(err.message);
});
};
Этот код обеспечивает скачивание файла по указанному URL и сохранение его в указанное местоположение. Важно тщательно обрабатывать ошибки: если возникает ошибка при выполнении HTTP-запроса, файл, который мог быть частично загружен, будет удалён, и функция обратного вызова (cb) будет вызвана с сообщением об ошибке.
Конечно! Вот перевод ответа в стиле StackOverflow:
Ваш код для загрузки файла с использованием http
и fs
выглядит неплохо, но имейте в виду, что если не дождаться события finish
, вы можете получить неполный файл. Ниже приведён улучшенный вариант с учётом правильного контроля потока и использования callback
.
var http = require('http');
var fs = require('fs');
var download = function(url, dest, cb) {
var file = fs.createWriteStream(dest);
http.get(url, function(response) {
response.pipe(file);
// Дожидаемся завершения записи, чтобы вызвать callback
file.on('finish', function() {
file.close(cb); // Передаём cb в close
});
}).on('error', function(err) { // Обработка ошибок
fs.unlink(dest); // Удаляем файл, если возникла ошибка
if (cb) cb(err.message); // Вызываем callback с ошибкой
});
}
Обратите внимание, что я добавил обработку ошибок, чтобы при сбое загрузки файл удалялся, а колбек вызывался с сообщением об ошибке. Также благодаря подсказке @Augusto Roman важно передать cb
в метод file.close
, а не вызывать его напрямую. Это гарантирует, что колбек будет вызван только после успешного завершения записи файла.
Надеюсь, это поможет! Если у вас возникнут дополнительные вопросы, не стесняйтесь спрашивать.
В ответе gfxmonk действительно наблюдается "гонка данных" между вызовом колбэка и завершением file.close()
. Фактически, file.close()
принимает колбэк, который вызывается после завершения операции закрытия. В противном случае, немедленное использование файла может привести к сбоям (хотя это происходит довольно редко!).
Полное решение будет выглядеть следующим образом:
var http = require('http');
var fs = require('fs');
var download = function(url, dest, cb) {
var file = fs.createWriteStream(dest);
var request = http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
file.close(cb); // close() является асинхронным, вызываем cb после завершения close.
});
});
}
Если не дожидаться события finish
, простые скрипты могут оказаться с неполным файлом. Не расписав вызов колбэка cb
через close
, вы можете столкнуться с гонкой между доступом к файлу и тем, что файл на самом деле не готов к использованию.
Похоже, что node.js изменился, и действительно есть некоторые проблемы с другими решениями (используя node v8.1.2):
- Не нужно вызывать
file.close()
в событииfinish
. По умолчаниюfs.createWriteStream
настроен на автоматическое закрытие: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options file.close()
следует вызывать при возникновении ошибки. Возможно, это не нужно, если файл удаляется (unlink()
), но как правило это необходимо: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options- Временный файл не удаляется, если
statusCode !== 200
. fs.unlink()
без колбэка считается устаревшим (выводит предупреждение).- Если файл назначения (
dest
) существует, он будет перезаписан.
Ниже приведено модифицированное решение (используя ES6 и промисы), которое учитывает указанные проблемы.
const http = require("http");
const fs = require("fs");
function download(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest, { flags: "wx" });
const request = http.get(url, response => {
if (response.statusCode === 200) {
response.pipe(file);
} else {
file.close(); // Закрываем файл при ошибочном ответе
fs.unlink(dest, () => {}); // Удаляем временный файл
reject(`Сервер ответил кодом ${response.statusCode}: ${response.statusMessage}`);
}
});
request.on("error", err => {
file.close(); // Закрываем файл при ошибке запроса
fs.unlink(dest, () => {}); // Удаляем временный файл
reject(err.message);
});
file.on("finish", () => {
resolve(); // Загруженный файл успешно завершен
});
file.on("error", err => {
file.close(); // Закрываем файл при ошибке записи
if (err.code === "EEXIST") {
reject("Файл уже существует");
} else {
fs.unlink(dest, () => {}); // Удаляем временный файл
reject(err.message);
}
});
});
}
Это решение исправляет перечисленные проблемы и предоставляет более надежный способ для скачивания файлов с использованием Node.js.
На основе других ответов и некоторых тонких моментов, вот мой подход.
- Проверьте, что файл не существует перед обращением к сети, используя
fs.access
. - Создавайте
fs.createWriteStream
только если получен статус-код200 OK
. Это уменьшает количество командfs.unlink
, необходимых для очистки временных файлов. - Даже при статусе
200 OK
мы всё равно можем получитьreject
из-за ошибкиEEXIST
, если файл уже существует (представьте, что другой процесс создал файл, пока мы выполняли сетевые запросы). - Рекурсивно вызывайте
download
, если получаете301 Moved Permanently
или302 Found (Moved Temporarily)
, следуя за ссылкой, указанной в заголовке. - Проблема с некоторыми другими ответами, которые рекурсивно вызывали
download
, заключалась в том, что они вызывалиresolve(download)
вместоdownload(...).then(() => resolve())
, поэтомуPromise
возвращался до того, как загрузка завершилась. Таким образом, вложенная цепочка обещаний разрешалась в правильном порядке. - Может показаться классным очищать временный файл асинхронно, но я выбрал отклонение только после завершения этой операции, чтобы быть уверенным, что всё завершено от начала до конца, когда это обещание разрешается или отклоняется.
const https = require('https');
const fs = require('fs');
/**
* Загружает ресурс по `url` в `dest`.
* @param {string} url - Действительный URL для загрузки ресурса
* @param {string} dest - Действительный путь для сохранения файла.
* @returns {Promise<void>} - Возвращает асинхронно при успешном завершении загрузки
*/
function download(url, dest) {
return new Promise((resolve, reject) => {
// Проверка, что файл еще не существует перед обращением к сети
fs.access(dest, fs.constants.F_OK, (err) => {
if (err === null) reject('Файл уже существует');
const request = https.get(url, response => {
if (response.statusCode === 200) {
const file = fs.createWriteStream(dest, { flags: 'wx' });
file.on('finish', () => resolve());
file.on('error', err => {
file.close();
if (err.code === 'EEXIST') reject('Файл уже существует');
else fs.unlink(dest, () => reject(err.message)); // Удаляем временный файл
});
response.pipe(file);
} else if (response.statusCode === 302 || response.statusCode === 301) {
// Рекурсивно следуем за редиректами, только 200 разрешит промис.
download(response.headers.location, dest).then(() => resolve());
} else {
reject(`Сервер ответил с кодом ${response.statusCode}: ${response.statusMessage}`);
}
});
request.on('error', err => {
reject(err.message);
});
});
});
}
Запись в файлы в Node.js
Как добавить данные в файл в Node?
Как получить полный объект в console.log() Node.js, а не '[Object]'?
Как прочитать JSON-файл в память сервера?
Обнаружена ошибка: Невозможное нарушение: Неверный тип элемента: ожидался строковый тип (для встроенных компонентов) или класс/функция, но получен объект