嵌套异步在异步呼叫是否可取?(node.js)



我正在使用node.js播放,我创建了一个简单的脚本,将文件从目录上传到服务器:

var request = require('request');
var file = require('file');
var fs = require('fs');
var path = require('path');

VERSION = '0.1'
CONFIG_FILE = path.join(__dirname, 'etc', 'sender.conf.json');

var config = JSON.parse(
  fs.readFileSync(CONFIG_FILE).toString()
);
var DATA_DIR = __dirname
config['data_dir'].forEach(function(dir) {
  DATA_DIR = path.join(DATA_DIR, dir)
});

console.log('sending data from root directory: ' + DATA_DIR);
file.walk(
  DATA_DIR,
  function(err, dir_path, dirs, files) {
    if(err) {
      return console.error(err);
    } 
    sendFiles(dir_path, files);
  } 
);
function sendFiles(dir_path, files)
{
  files
    .filter(function(file) {
      return file.substr(-5) === '.meta';
    })
    .forEach(function(file) {
      var name = path.basename(file.slice(0, -5));
      sendFile(dir_path, name);
    })
  ; 
} 
function sendFile(dir_path, name)
{
  console.log("reading file start: " + dir_path + "/" + name);
  fs.readFile(
    path.join(dir_path, name + '.meta'),
    function(err, raw_meta) {
      if(err) {
        return console.error(err);
      }
      console.log("reading file done: " + dir_path + "/" + name);
      sendData(
        name,
        JSON.parse(raw_meta),
        fs.createReadStream(path.join(dir_path, name + '.data'))
      );
    }
  );
  console.log("reading file async: " + dir_path + "/" + name);
}
function sendData(name, meta, data_stream)
{ 
  meta['source'] = config['data_source'];
  var req = request.post(
    config['sink_url'],
    function(err, res, body) {
      if(err) {
        console.log(err);
      }
      else {
        console.log(name);
        console.log(meta);
        console.log(body);
      }
    }
  );
  var form = req.form();
  form.append(
    'meta',
    JSON.stringify(meta),
    { 
      contentType: 'application/x-www-form-urlencoded'
    }
  );
  form.append(
    'data',
    data_stream
  );
}

仅使用几个文件运行时,它可以正常工作。但是,当我用大量文件在目录上运行它时,它会窒息。这是因为它不断创建大量的任务以从文件中读取,但从来没有真正进行读取(因为文件太多(。可以在输出上观察到:

sending data from root directory: .../data
reading file start: .../data/ac/ad/acigisu-adruire-sabeveab-ozaniaru-fugeef-wemathu-lubesoraf-lojoepe
reading file async: .../data/ac/ad/acigisu-adruire-sabeveab-ozaniaru-fugeef-wemathu-lubesoraf-lojoepe
reading file start: .../data/ac/ab/acodug-abueba-alizacod-ugvut-nucom
reading file async: .../data/ac/ab/acodug-abueba-alizacod-ugvut-nucom
reading file start: .../data/ac/as/acigisu-asetufvub-liwi-ru-mitdawej-vekof
reading file async: .../data/ac/as/acigisu-asetufvub-liwi-ru-mitdawej-vekof
reading file start: .../data/ac/av/ace-avhad-bop-rujan-pehwopa
reading file async: .../data/ac/av/ace-avhad-bop-rujan-pehwopa
...

对于每个文件,在拨打fs.readFile之前立即产生了控制台输出"reading file start",并且在安排了异步读数后立即生成的"reading file async"。但是,即使我让它运行很长时间,也没有"reading file done"消息,这意味着任何文件的读数都可能从未被安排(这些文件按100个字节的顺序订购,因此一旦安排了,这些文件可能会完成在单一中(。

这使我进入以下思维过程。在node.js中的异步调用是因为事件循环本身是单线程,我们不想阻止它。,一旦满足了此要求,将进一步的异步调用嵌套到异步呼叫中本身会嵌套在异步呼叫等中,等等。此外,是否由于安排开销而不需要真正需要的代码的实际情况,如果完全处理单个文件仅由同步调用组成,则可以完全避免?

鉴于上面的思考过程,我的行动是从这个问题中使用解决方案:

  • 异步将所有文件的名称推入async.queue
  • 通过设置queue.concurrency
  • 来限制并行任务的限制数量
  • 提供完全同步的文件 - upload处理程序,即,它同步读取文件的内容,然后完成后,它同步将POST请求发送到服务器

这是我第一次尝试使用node.js和/或javaScript,因此我很可能完全错了(请注意,例如,同步呼叫的呼叫非常清楚,同步呼叫是不需要的,与我上面的思考过程矛盾 - 问题是为什么(。关于上述思维过程有效性的任何评论以及提议的解决方案的可行性以及最终的替代方案。

== update ==

有很好的文章,直接在Node.js。

的文档中详细解释了所有这些

至于手头的特定问题,它确实是在选择文件系统 - 步行器模块的选择。解决方案是使用例如步行而不是文件:

@@ -4,7 +4,7 @@

 var request = require('request');
-var file = require('file');
+var walk = require('walk');
 var fs = require('fs');
 var path = require('path');
@@ -24,13 +24,19 @@ config['data_dir'].forEach(function(dir) {

 console.log('sending data from root directory: ' + DATA_DIR);
-file.walk(
-  DATA_DIR,
-  function(err, dir_path, dirs, files) {
-    if(err) {
-      return console.error(err);
-    }
-    sendFiles(dir_path, files);
+var walker = walk.walk(DATA_DIR)
+walker.on(
+  'files',
+  function(dir_path, files, next) {
+    sendFiles(dir_path, files.map(function(stats) { return stats.name; }));
+    next();
+  }
+);
+walker.on(
+  'errors',
+  function(dir_path, node_stats, next) {
+    console.error('file walker:', node_stats);
+    next();
   }
 );

==原始post ==

经过更多的研究,我将尝试回答自己的问题。这个答案仍然只是一个部分解决方案(来自具有Node.js实际经验的人的更完整答案将不胜感激(。

上面主要问题的简短答案是,确实确实是可取的,而且几乎总是必要的,以安排来自已经异步函数的更多异步函数

以下是长的解释。

这是因为Node.js计划的工作方式:"除了我们的代码外,所有内容都在其他线程上运行。"。链接的博客文章的讨论中有两个非常重要的评论:

  • " JavaScript总是首先完成当前执行功能。事件永远不会中断功能。" [Twitchard]
  • "还要注意,它不仅完成当前函数,它将运行到所有同步函数的完成,我相信process.nexttick ... nexttick ...在请求之前回调是处理的。" [Tim Oxley]

process.nextTick的文档中也提到了这一点。回调将阻止任何I/O发生,就像一段时间一样(true(;循环。"

因此,总而言之,脚本本身的所有代码仅在单线程和单个线程上运行。计划运行的异步回调在同一单程上执行,并且仅在整个当前下一个tick队列排干后才执行。使用异步回调的使用提供了唯一的点,即可以计划运行其他一些功能。如果File-Upload处理程序不会按照问题所述安排任何其他异步任务,则其执行将阻止其他所有内容,直到整个文件upload处理程序都将完成为止。这是不可取的。

这也解释了为什么输入文件的实际读取永远不会发生("递归设置NextTick回调将阻止发生的任何I/O的发生" - 请参见上文(。在整个目录层次结构的所有任务都将被安排在整个目录层次结构之后,它最终将发生。但是,没有进一步的研究,我无法回答该问题如何限制安排的文件upload任务数量(有效地大小的任务队列大小(并阻止调度循环,直到处理某些任务(任务队列上的某个房间已释放(。因此,此答案仍然不完整。

最新更新