fs.createWriteStream не создает файл сразу?
Я сделал простую загрузку из http-функции, как показано ниже (обработка ошибок опущена для упрощения):
function download(url, tempFilepath, filepath, callback) {
var tempFile = fs.createWriteStream(tempFilepath);
http.request(url, function(res) {
res.on('data', function(chunk) {
tempFile.write(chunk);
}).on('end', function() {
tempFile.end();
fs.renameSync(tempFile.path, filepath);
return callback(filepath);
})
});
}
Однако, поскольку я вызываю download()
десятки раз асинхронно, он редко сообщает об ошибке на fs.renameSync
жалуясь, что не может найти файл на tempFile.path
.
Error: ENOENT, no such file or directory 'xxx'
Я использовал тот же список URL-адресов, чтобы проверить его, и он провалил около 30% времени. Тот же список URL-адресов работал при загрузке по одному.
Тестируя еще несколько, я узнал, что следующий код
fs.createWriteStream('anypath');
console.log(fs.exist('anypath'));
console.log(fs.exist('anypath'));
console.log(fs.exist('anypath'));
не всегда печатает true
, но иногда первый ответ печатает false
.
Я подозреваю, что слишком много асинхронных вызовов fs.createWriteStream
не могут гарантировать создание файла. Это правда? Существуют ли какие-либо способы гарантировать создание файлов?
Ответы
Ответ 1
Вы не должны вызывать write
в tempFile
записи tempFile
до тех пор, пока вы не получите 'open'
событие из потока. Файл не будет существовать, пока вы не увидите это событие.
Для вашей функции:
function download(url, tempFilepath, filepath, callback) {
var tempFile = fs.createWriteStream(tempFilepath);
tempFile.on('open', function(fd) {
http.request(url, function(res) {
res.on('data', function(chunk) {
tempFile.write(chunk);
}).on('end', function() {
tempFile.end();
fs.renameSync(tempFile.path, filepath);
return callback(filepath);
});
});
});
}
Для вашего теста:
var ws = fs.createWriteStream('anypath');
ws.on('open', function(fd) {
console.log(fs.existsSync('anypath'));
console.log(fs.existsSync('anypath'));
console.log(fs.existsSync('anypath'));
});
Ответ 2
Принятый ответ не загружал для меня некоторые из последних байтов.
Здесь версия Q, которая работает правильно (но без временного файла).
'use strict';
var fs = require('fs'),
http = require('http'),
path = require('path'),
Q = require('q');
function download(url, filepath) {
var fileStream = fs.createWriteStream(filepath),
deferred = Q.defer();
fileStream.on('open', function () {
http.get(url, function (res) {
res.on('error', function (err) {
deferred.reject(err);
});
res.pipe(fileStream);
});
}).on('error', function (err) {
deferred.reject(err);
}).on('finish', function () {
deferred.resolve(filepath);
});
return deferred.promise;
}
module.exports = {
'download': download
};
Примечание. Я слушаю, как finish
в потоке файлов, а не в end
ответа.
Ответ 3
Вот что я использую, чтобы сделать это:
function download(url, dest) {
return new Promise((resolve, reject) => {
http.get(url, (res) => {
if (res.statusCode !== 200) {
var err = new Error('File couldn\'t be retrieved');
err.status = res.statusCode;
return reject(err);
}
var chunks = [];
res.setEncoding('binary');
res.on('data', (chunk) => {
chunks += chunk;
}).on('end', () => {
var stream = fs.createWriteStream(dest);
stream.write(chunks, 'binary');
stream.on('finish', () => {
resolve('File Saved !');
});
res.pipe(stream);
})
}).on('error', (e) => {
console.log("Error: " + e);
reject(e.message);
});
})
};
Ответ 4
Я работаю над загрузкой и загрузкой файлов (docx, pdf, text и т.д.) Через библиотеки nodejs request-promise
и request
.
Проблема с request-promise
заключается в том, что они не обещают метод pipe
из пакета request
. Следовательно, мы должны сделать это по-старому.
Мне удалось придумать гибридное решение, в котором я мог использовать async/await
и Promise()
одновременно. Вот пример:
/**
* Downloads the file.
* @param {string} fileId : File id to be downloaded.
* @param {string} downloadFileName : File name to be downloaded.
* @param {string} downloadLocation : File location where it will be downloaded.
* @param {number} version : [Optional] version of the file to be downloaded.
* @returns {string}: Downloaded file absolute path.
*/
const getFile = async (fileId, downloadFileName, downloadLocation, version = undefined) => {
try {
const url = version ? 'http://localhost:3000/files/${fileId}?version=${version}' :
'${config.dms.url}/files/${fileUuid}';
const fileOutputPath = path.join(downloadLocation, fileName);
const options = {
method: 'GET',
url: url,
headers: {
'content-type': 'application/json',
},
resolveWithFullResponse: true
}
// Download the file and return the full downloaded file path.
const downloadedFilePath = writeTheFileIntoDirectory(options, fileOutputPath);
return downloadedFilePath;
} catch (error) {
console.log(error);
}
};
Как вы можете видеть в приведенном выше методе getFile
, мы используем последние функции, поддерживаемые ES async/await
, для асинхронного программирования. Теперь давайте рассмотрим метод writeTheFileIntoDirectory
.
/**
* Makes REST API request and writes the file to the location provided.
* @param {object} options : Request option to make REST API request.
* @param {string} fileOutputPath : Downloaded file absolute path.
*/
const writeTheFileIntoDirectory = (options, fileOutputPath) => {
return new Promise((resolve, reject) => {
// Get file downloaded.
const stream = fs.createWriteStream(fileOutputPath);
return request
.get(options.url, options, (err, res, body) => {
if (res.statusCode < 200 || res.statusCode >= 400) {
const bodyObj = JSON.parse(body);
const error = bodyObj.error;
error.statusCode = res.statusCode;
return reject(error);
}
})
.on('error', error => reject(error))
.pipe(stream)
.on('close', () => resolve(fileOutputPath));
});
}
Прелесть nodejs в том, что он поддерживает обратную совместимость различных асинхронных реализаций. Если метод возвращает обещание, то await
будет активирован и будет ожидать завершения метода.
Выше метод writeTheFileIntoDirectory
загрузит файл и вернется положительно, когда поток будет успешно закрыт, иначе он вернет ошибку.