Node.js 使用非阻塞IO对文件收集和聚合结果报告的操作

Node.js 使用非阻塞IO对文件收集和聚合结果报告的操作,node.js,io,report,nonblocking,operation,Node.js,Io,Report,Nonblocking,Operation,我想在任意大的文件集上执行一些任意昂贵的工作。我想实时报告进度,然后在处理完所有文件后显示结果。如果没有与我的表达式匹配的文件,我想抛出一个错误 想象一下,编写一个测试框架,加载所有测试文件,执行它们(没有特定顺序),实时报告进度,然后在所有测试完成后显示聚合结果 用块语言(比如Ruby)编写此代码非常简单 事实证明,我在节点中执行这个看似简单的任务时遇到了困难,同时也无法真正利用异步、基于事件的IO 我的第一个设计是连续执行每个步骤 加载所有文件,创建要处理的文件集合 处理集合中的每个文件 处

我想在任意大的文件集上执行一些任意昂贵的工作。我想实时报告进度,然后在处理完所有文件后显示结果。如果没有与我的表达式匹配的文件,我想抛出一个错误

想象一下,编写一个测试框架,加载所有测试文件,执行它们(没有特定顺序),实时报告进度,然后在所有测试完成后显示聚合结果

用块语言(比如Ruby)编写此代码非常简单

事实证明,我在节点中执行这个看似简单的任务时遇到了困难,同时也无法真正利用异步、基于事件的IO

我的第一个设计是连续执行每个步骤

  • 加载所有文件,创建要处理的文件集合
  • 处理集合中的每个文件
  • 处理完所有文件后报告结果
  • 这种方法确实有效,但对我来说似乎不太正确,因为它会导致程序中计算成本较高的部分等待所有文件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的一种非常“节点”的方式,但它存在两个主要问题(至少在我假定的错误实现中):

  • 我不知道所有的文件什么时候都处理好了,所以我不知道什么时候组装和发布结果
  • 因为文件读取是非阻塞的,并且是递归的,所以我很难知道是否没有找到文件
  • 我希望我只是做错了什么,并且有一些其他人使用的合理简单的策略来让第二种方法起作用

    尽管这个例子使用了一个测试框架,但我有许多其他项目遇到了完全相同的问题,我想任何人编写一个相当复杂的应用程序来访问node中的文件系统都会遇到这个问题。

    你所说的“读取并处理testFile的内容”是什么意思

    我不明白你为什么不知道所有的文件都是什么时候处理的。您没有使用流吗?流具有多个事件,而不仅仅是
    数据
    。如果您处理
    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(“>>找到的文件:“+文件”);
    *     }
    *   });
    *
    *下面是一个等待的示例