Node.js 使用非阻塞IO对文件收集和聚合结果报告的操作
我想在任意大的文件集上执行一些任意昂贵的工作。我想实时报告进度,然后在处理完所有文件后显示结果。如果没有与我的表达式匹配的文件,我想抛出一个错误 想象一下,编写一个测试框架,加载所有测试文件,执行它们(没有特定顺序),实时报告进度,然后在所有测试完成后显示聚合结果 用块语言(比如Ruby)编写此代码非常简单 事实证明,我在节点中执行这个看似简单的任务时遇到了困难,同时也无法真正利用异步、基于事件的IO 我的第一个设计是连续执行每个步骤Node.js 使用非阻塞IO对文件收集和聚合结果报告的操作,node.js,io,report,nonblocking,operation,Node.js,Io,Report,Nonblocking,Operation,我想在任意大的文件集上执行一些任意昂贵的工作。我想实时报告进度,然后在处理完所有文件后显示结果。如果没有与我的表达式匹配的文件,我想抛出一个错误 想象一下,编写一个测试框架,加载所有测试文件,执行它们(没有特定顺序),实时报告进度,然后在所有测试完成后显示聚合结果 用块语言(比如Ruby)编写此代码非常简单 事实证明,我在节点中执行这个看似简单的任务时遇到了困难,同时也无法真正利用异步、基于事件的IO 我的第一个设计是连续执行每个步骤 加载所有文件,创建要处理的文件集合 处理集合中的每个文件 处
eachFileMatching(path, expression, callback) {
// recursively, asynchronously traverse the file system,
// calling callback every time a file name matches expression.
}
这种方法的消费者看起来像这样:
eachFileMatching('test/', /_test.js/, function(err, testFile) {
// read and process the content of testFile
});
虽然这种设计感觉像是处理IO的一种非常“节点”的方式,但它存在两个主要问题(至少在我假定的错误实现中):
数据
。如果您处理end
事件,那么您将知道每个文件何时完成
例如,您可能有一个文件名列表
,为每个文件设置处理,然后当您收到结束
事件时,从列表中删除该文件名。当列表为空时,您就完成了。或者创建包含名称和完成状态的FileName对象。当您得到一个end
事件时,更改状态并减少文件名计数器。当计数器为零时,您就完成了,或者如果您没有信心,您可以扫描所有文件名对象,以确保其状态已完成
您还可能有一个计时器,它会定期检查计数器,如果计数器在一段时间内没有改变,则报告处理可能会停留在状态未完成的文件名对象上
。。。我只是在另一个问题中遇到了这种情况,而公认的答案(加上github链接)很好地解释了这一点。检查一下这两种方法,首先可能是连续考虑的
var files = [];
doFile(files, oncomplete);
function doFile(files, oncomplete) {
if (files.length === 0) return oncomplete();
var f = files.pop();
processFile(f, function(err) {
// Handle error if any
doFile(files, oncomplete); // Recurse
});
};
function processFile(file, callback) {
// Do whatever you want to do and once
// done call the callback
...
callback();
};
第二种方式,我们称之为并行,类似于:
var files = [];
doFiles(files, oncomplete);
function doFiles(files, oncomplete) {
var exp = files.length;
var done = 0;
for (var i = 0; i < exp; i++) {
processFile(files[i], function(err) {
// Handle errors (but still need to increment counter)
if (++done === exp) return oncomplete();
});
}
};
function processFile(file, callback) {
// Do whatever you want to do and once
// done call the callback
...
callback();
};
var文件=[];
doFiles(文件,oncomplete);
函数doFiles(文件,oncomplete){
var exp=files.length;
var done=0;
对于(变量i=0;i
现在似乎很明显,您应该使用第二种方法,但您会发现,对于IO密集型操作,并行化时并没有真正获得任何性能提升。第一种方法的一个优点是递归可能会破坏堆栈跟踪
Tnx
Guido事实证明,我能够构建的最小工作解决方案比我希望的要复杂得多 下面是适合我的代码。它可能会被清理或者变得更容易阅读,我对这样的反馈不感兴趣 如果有一种明显不同的方法来解决这个问题,那就是更简单和/或更有效,我很有兴趣听一听。这个看似简单的需求的解决方案需要如此大量的代码,这真的让我感到惊讶,但也许这就是为什么有人发明了阻塞io 复杂性实际上是为了满足以下所有要求:
- 处理找到的文件
- 知道搜索何时完成
- 知道是否找不到文件
/**
* Call fileHandler with the file name and file Stat for each file found inside
* of the provided directory.
*
* Call the optionally provided completeHandler with an array of files (mingled
* with directories) and an array of Stat objects (one for each of the found
* files.
*
* Following is an example of a simple usage:
*
* eachFileOrDirectory('test/', function(err, file, stat) {
* if (err) throw err;
* if (!stat.isDirectory()) {
* console.log(">> Found file: " + file);
* }
* });
*
* Following is an example that waits for all files and directories to be
* scanned and then uses the entire result to do something:
*
* eachFileOrDirectory('test/', null, function(files, stats) {
* if (err) throw err;
* var len = files.length;
* for (var i = 0; i < len; i++) {
* if (!stats[i].isDirectory()) {
* console.log(">> Found file: " + files[i]);
* }
* }
* });
*/
var eachFileOrDirectory = function(directory, fileHandler, completeHandler) {
var filesToCheck = 0;
var checkedFiles = [];
var checkedStats = [];
directory = (directory) ? directory : './';
var fullFilePath = function(dir, file) {
return dir.replace(/\/$/, '') + '/' + file;
};
var checkComplete = function() {
if (filesToCheck == 0 && completeHandler) {
completeHandler(null, checkedFiles, checkedStats);
}
};
var onFileOrDirectory = function(fileOrDirectory) {
filesToCheck++;
fs.stat(fileOrDirectory, function(err, stat) {
filesToCheck--;
if (err) return fileHandler(err);
checkedFiles.push(fileOrDirectory);
checkedStats.push(stat);
fileHandler(null, fileOrDirectory, stat);
if (stat.isDirectory()) {
onDirectory(fileOrDirectory);
}
checkComplete();
});
};
var onDirectory = function(dir) {
filesToCheck++;
fs.readdir(dir, function(err, files) {
filesToCheck--;
if (err) return fileHandler(err);
files.forEach(function(file, index) {
file = fullFilePath(dir, file);
onFileOrDirectory(file);
});
checkComplete();
});
}
onFileOrDirectory(directory);
};
/**
*调用fileHandler,为其中找到的每个文件指定文件名和文件状态
*提供的目录的名称。
*
*使用文件数组(混合)调用可选提供的completeHandler
*带有目录)和一个Stat对象数组(找到的每个
*档案。
*
*下面是一个简单用法的示例:
*
*每个文件目录('test/',函数(err,file,stat){
*如果(错误)抛出错误;
*如果(!stat.isDirectory()){
*log(“>>找到的文件:“+文件”);
* }
* });
*
*下面是一个等待的示例