fs.createWriteStream 不会立即创建文件?

Posted

技术标签:

【中文标题】fs.createWriteStream 不会立即创建文件?【英文标题】:fs.createWriteStream does not immediately create file? 【发布时间】: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 调用不能保证文件的创建。这是真的?有什么方法可以保证文件的创建?

【问题讨论】:

【参考方案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方法会下载文件,当流关闭成功时返回正向,否则返回错误。

【讨论】:

以上是关于fs.createWriteStream 不会立即创建文件?的主要内容,如果未能解决你的问题,请参考以下文章

fs.createwritestream 不是函数

Node.js:检测到一个文件,用 fs.createWriteStream() 打开,被删除

我可以从 fs.createWriteStream() 获取缓冲区吗?

使用 Fetch API 和 fs.createWriteStream 对文件进行流式响应

node.js 中与 fs.createWriteStream 关​​联的事件

node.js fs.createWriteStream 创建文件,但无法正确写入