我正在玩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
...


对于每个文件,在调用"reading file start"之前立即生成控制台输出fs.readFile,在安排了异步读取之后立即生成"reading file async"。但是,即使我让它运行很长时间也没有"reading file done"消息,这意味着任何文件的读取都可能甚至从未计划过(这些文件的字节数为100字节,因此一旦计划好,这些读取就会大概一次完成)。

这导致我进入以下思考过程。之所以能够完成Node.js中的异步调用,是因为事件循环本身是单线程的,我们不想阻止它。但是,一旦满足此要求,将进一步的异步调用嵌套到本身嵌套在异步调用等中的异步调用中是否有意义?它有任何特定目的吗?此外,由于调度开销并不是真正需要的,因此它不是对代码的实际悲观化吗?如果单个文件的完全处理仅由同步调用组成,则可以完全避免这种开销?

鉴于上述思考过程,我的行动方针是使用this question中的解决方案:


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


这是我第一次尝试使用Node.js和/或JavaScript,因此很有可能我完全错了(请注意,例如sync-request package明确表明不希望使用同步调用,这与我的想法相矛盾。上面的过程-问题是为什么)。对于上述思考过程的有效性以及所提出解决方案的可行性以及最终替代方案的任何评论,将不胜感激。

最佳答案

==更新==

非常好的article直接在Node.js文档中详细解释了所有这些内容。

至于眼前的特殊问题,实际上是在选择文件系统行者模块。解决方案是使用例如walk代替file

@@ -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();
   }
 );


==原始帖子==

经过更多的研究,我将尝试回答我自己的问题。这个答案仍然只是部分解决方案(非常感谢拥有Node.js实际经验的人提供的完整答案)。

对上述主要问题的简短回答是,从既有异步函数调度更多异步函数的确不仅是理想的,而且几乎总是必需的。详细说明如下。

这是因为Node.js调度的工作原理:"Everything runs on a different thread except our code."。在链接的博客文章下面的讨论中,有两个非常重要的评论:


“ JavaScript总是首先完成当前正在执行的功能。事件永远不会中断功能。” [Twitchard]
“还要注意,它不仅会完成当前功能,还会运行到所有同步功能的完成,并且我相信在处理请求回调之前,所有与process.nextTick排队的东西都将排队。” [Tim Oxley]


process.nextTick的文档中也有一条注释提到了这一点:“在处理其他I / O之前,事件循环的每个遍历中都完全耗尽了下一个滴答队列。因此,递归设置nextTick回调将阻止任何I / O发生了,就像一阵(true);循环。”

因此,总而言之,脚本本身的所有代码都在单线程和单线程上运行。计划运行的异步回调在同一线程上执行,并且仅在耗尽整个当前的下一个滴答队列之后才执行。当可以安排其他一些函数运行时,使用异步回调是唯一的选择。如果文件上载处理程序不按问题中所述安排任何其他异步任务,则它的执行将阻止其他所有操作,直到整个文件上载处理程序完成。那是不可取的。

这也解释了为什么从未真正读取输入文件的原因(“递归设置nextTick回调将阻止发生任何I / O”-参见上文)。在计划遍历整个目录层次结构的所有任务之后,最终会发生这种情况。但是,如果不做进一步研究,我将无法回答以下问题:如何限制已调度的文件上传任务的数量(有效地是任务队列的大小),以及如何阻止调度循环,直到其中一些任务已被处理(一些空间)任务队列上的已释放)。因此,这个答案仍然不完整。

关于node.js - 将异步调用嵌套在异步调用中是否可取? (Node.js),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43518318/

10-16 13:03