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'));
);
【讨论】:
不知何故,这段代码没有下载最后的一些字节。相反,我现在在tempFile
和res.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-promise
和 request
库上传和下载文件(docx、pdf、文本等)。
request-promise
的问题是他们没有从 request
包中承诺 pipe
方法。因此,我们需要以旧方式进行。
我想出了混合解决方案,我可以同时使用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 的美妙之处在于它支持不同异步实现的向后兼容。如果一个方法正在返回 promise,那么await
将被启动并等待该方法完成。
以上writeTheFileIntoDirectory
方法会下载文件,当流关闭成功时返回正向,否则返回错误。
【讨论】:
以上是关于fs.createWriteStream 不会立即创建文件?的主要内容,如果未能解决你的问题,请参考以下文章
Node.js:检测到一个文件,用 fs.createWriteStream() 打开,被删除
我可以从 fs.createWriteStream() 获取缓冲区吗?
使用 Fetch API 和 fs.createWriteStream 对文件进行流式响应