【问题标题】:fs.createWriteStream does not immediately create file?fs.createWriteStream 不会立即创建文件?
【发布时间】:2012-10-06 01:55:47
【问题描述】:

我做了一个简单的从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 调用不能保证文件的创建。这是真的?有什么方法可以保证文件的创建?

【问题讨论】:

    标签: node.js


    【解决方案1】:

    在您收到来自流的'open' 事件之前,您不应在tempFile 写入流上调用write。在您看到该事件之前,该文件不会存在。

    对于你的功能:

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

    【讨论】:

    • 不知何故,这段代码没有下载最后的一些字节。相反,我现在在tempFileres.pipe(tempFile) 上收听finish,而不是手动进行。
    • fs.js source 表示在写入流上调用write 之前无需等待'open' 事件,因为这是在内部处理的。
    • open 确实是在内部处理的,但它是异步的,文件在open 事件发出之前不会打开。
    【解决方案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】:

        我正在通过 nodejs request-promiserequest 库上传和下载文件(docx、pdf、文本等)。

        request-promise 的问题是他们没有从 request 包中承诺 pipe 方法。因此,我们需要以旧方式进行。

        我想出了混合解决方案,我可以同时使用async/awaitPromise()。示例如下:

            /**
             * 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 的美妙之处在于它支持不同异步实现的向后兼容。如果一个方法正在返回 promise,那么await 将被启动并等待该方法完成。

        以上writeTheFileIntoDirectory方法会下载文件,当流关闭成功时返回正向,否则返回错误。

        【讨论】:

          猜你喜欢
          • 2017-09-23
          • 1970-01-01
          • 2018-12-02
          • 2015-07-16
          • 2019-08-16
          • 2022-11-14
          • 2015-08-10
          • 2013-07-16
          • 2017-06-25
          相关资源
          最近更新 更多