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 загрузит файл и вернется положительно, когда поток будет успешно закрыт, иначе он вернет ошибку.