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 звонки не могут гарантировать создание файла. Это правда? Существуют ли способы гарантировать создание файла?

3 ответа

Решение

Ты не должен звонить write на ваше 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'));
});

Принятый ответ не загрузил некоторые из последних байтов для меня.
Вот версия 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 на ответ.

Вот что я использую, чтобы сделать это:

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);
        });
    })
};

Я работаю над загрузкой и скачиванием файла (docx, pdf, текст и т. Д.) Через 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's 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's 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 Метод загрузит файл и вернет положительный результат, когда поток будет успешно закрыт, иначе он вернет ошибку.

Другие вопросы по тегам