如何顺序写入新文件

时间:2017-12-08 19:36:57

标签: node.js

我有一个文件目录(下面是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)

1 个答案:

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