我有一个文件目录(下面是files
dir),每个文件都有正在处理的数据(并与初始文件data/phrase.js
中的数据混合),然后才能写入转换后的数据(实际附加)到输出目录中的新文件。我的问题是每个文件的所有数据都在最后写入文件(在所有处理完成之后)。我宁愿处理第一个文件然后写入磁盘,然后处理第二个文件并写入磁盘等,以便在内存中保存较少的数据。 (虽然此示例中涉及的文件非常少,但在我的实际应用程序中,还有更多文件)
问题:为什么数据最后写入文件(一旦处理完所有文件)?有没有办法在数据准备好后立即将数据写入文件,而不是将其全部保存在内存中,直到每个文件的所有数据都准备就绪?
var fs = require('fs');
//file with some data
fs.readFile('./data/phrase.js', function(err, data){
var somephrase = data.toString();
//directory of many files
fs.readdir('./files/', (err, files) => {
files.forEach(file => {
let f = './files/' + file;
fs.readFile(f, (err, data2) => {
let somenumber = data2.toString();
//intermingle the data from initial file (phrase.js) with each of the files in files dir
let output = somenumber + somephrase;
//write output to new files
let output_file = './output/' + somenumber + 'js';
fs.appendFile(output_file, output,function(err){
if (err){
console.log("err")
}
});
});
});
});
});
phrase.js
cow jumped over the moon
文件/ one.js 1
文件/ two.js 2
输出
output/1.js (1 cow jumped over the moon)
output/2.js (2 cow jumped over the moon)
答案 0 :(得分:2)
为什么数据最后写入文件(一旦处理完所有文件)?
你的循环是同步的。您的文件操作是异步的。因此,你运行所有的循环并开始所有的文件操作,然后它们都有点并行运行,所以它们都会在一段时间后完成。
有没有办法在数据准备好后立即将数据写入文件,而不是将其全部保存在内存中,直到每个文件的所有数据都准备就绪?
在ES6中使用Promise和Await
要对文件写入进行排序,必须以不同方式编写异步代码。使用ES6,使用promises和await
会更容易一些。这是一个例子:
const fs = require('fs');
const util = require('util');
// create promisified versions of fs methods we will use
const readFile = util.promisify(fs.readFile);
const readdir = util.promisify(fs.readdir);
const appendfile = util.promisify(fs.appendFile);
async function run() {
let somephrase = await readFile('./data/phrase.js').toString();
let files = await readdir('./files');
for (let file of files) {
try {
let f = './files/' + file;
let somenumber = await readFile(f).toString();
//intermingle the data from initial file (phrase.js) with each of the files in files dir
let output = somenumber + somephrase;
//write output to new files
let output_file = './output/' + somenumber + 'js';
await appendFile(output_file, output);
} catch(e) {
console.log("error in loop", e);
}
}
}
run().then(() => {
// all done here
}).catch(err => {
// error occurred here
});
使用Promises和.reduce()序列化
如果您想在不使用await
的情况下执行此操作,则必须手动对操作进行排序。使用promises执行此操作的常见设计模式是将.reduce()
与链式承诺一起使用。一般模式如下:
array.reduce((p, item) => {
return p.then(() => {
return fn(item);
})
}, Promise.resolve().then(() => {
// iteration all done here
}).catch(err => {
// process error here
});
其中:
fn(item)
是您的函数,它返回为数组中的每个项调用的promise。如果需要,可以向函数调用添加更多参数。
并且,此模式可以应用于您的特定代码:
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
const readdir = util.promisify(fs.readdir);
const appendfile = util.pro
readFile('./data/phrase.js').then(data => {
return data.toString();
}).then(somephrase => {
return readdir('./files').then(files => {
return files.reduce((p, file) => {
return p.then(() => {
let f = './files/' + file;
return readFile(f).then(data => {
let output_file = './output/' + data.toString() + 'js';
let output = somenumber + somephrase;
return appendFile(output_file, output);
});
});
}, Promise.resolve());
});
}).then(() => {
// all done here
}).catch(err => {
// error occurred here
});
使用Bluebird承诺库
还有一个名为Bluebird的promise库可以使它更容易,因为它包含序列化功能和promisification功能:
const Promise = require('bluebird');
const fs = Promise.promsifyAll(require('fs'));
fs.readFileAsync('./data/phrase.js').then(data => {
let somephrase = data.toString();
return fs.readdirAsync('./files').then(files => {
// iterate array serially
return Promise.each(files, file => {
let f = './files/' + file;
return fs.readFileAsync(f).then(data => {
let output_file = './output/' + data.toString() + 'js';
let output = somenumber + somephrase;
return fs.appendFileAsync(output_file, output);
});
});
});
})