使用promises / defer异步迭代一组文件

时间:2016-02-04 23:42:27

标签: javascript node.js express promise

我的目标是遍历文件目录,对每个文件运行一些操作,并返回包含目录子集的json对象。

我在Node的fs库中使用同步版本的调用工作,但我想找出最好的异步解决方案。我在异步版本上尝试失败的原因是使用Q库推迟。无论我做什么,在迭代完成之前,我都无法推迟最后一步。

迭代成功完成,但不会在调用sendResponse()之前完成。

任何人都可以帮助我理解我做错了吗?

router.get('/mediaTree', function(req, res){
  var mediaTree = { "identifier" : "id", "label" : "name", "items" : []}; 
  var idCounter = 1;

  var fs_readdir = q.denodeify(fs.readdir);

  fs_readdir(MEDIAPATH)
    .then(function(files) {
        files.forEach(function(dir) { 
          fs.stat(MEDIAPATH + dir, function(err, stats) {
            if(err) { console.log(err); return; }

            var thisFile = {};
            if(stats.isDirectory()) {
             thisFile.id = idCounter++;
             thisFile.type = "branch";  
             thisFile.name = dir;
             thisFile.path = MEDIAPATH + dir;
             mediaTree.items.push(thisFile);  
            } 
          });  
        }); 
    })
   .then(sendResponse);


  function sendResponse() {  
    res.json(mediaTree);
  }
});

1 个答案:

答案 0 :(得分:1)

要使上述代码正常运行,您必须完全使用promises。有关承诺的详细信息,请参阅MDN或类似的来源。

鉴于此,你应该将fs.stat包装在一个promise中。这样,承诺管理等待您的结果,并为您提供以更加同步的方式运行大部分代码的选项。

var q = require('q'); // implied from your code sample
var path = require('path'); // joining paths with "+" might fail

router.get('/mediaTree', function(req, res){
    var qreaddir = q.denodeify(fs.readdir);
    var qstat = q.denodeify(fs.stat); 

    qreaddir(MEDIAPATH)
    .then(function(files) {
        // from inside out
        // - a promise for the fs.stat of a single file,
        // EDIT: Containing an object ob both the result and the dir
        // - an array of these promises created via Array.prototype.map from the files
        // - a promise from this array
        return q.all(files.map(function(dir) {
            return qstat(path.join(MEDIAPATH, dir))
                .then(function(stat) { // EDIT: extending the original answer
                    return {
                        dir: dir,   // the dir from the outer outer scope
                        stat: stat, // the stats result from the qstat promise
                    };
                });
        }));
    })
    .then(function(results) {
        // Promises should have no side effects, declare vars in function scope
        var mediaTree = {
            identifier: "id", 
            label: "name", 
            items: []
        };
        var idCounter = 1;

        // since we now have a sync array, we can continue as needed.
        results.forEach(function(result) {
            // EDIT: The original answer had the stats as immediate result
            // Now we get an object with both the dir and it's stat result.

            var stats = result.stats;
            var dir = result.dir; // Use this as needed.

            if (stats.isDirectory()) {
                var thisFile = {};

                thisFile.id = idCounter++;
                thisFile.type = "branch";  
                thisFile.name = dir;
                thisFile.path = path.join(MEDIAPATH, dir);

                mediaTree.items.push(thisFile);  
            }
        });

        return res.json(mediaTree);
    })
    .catch(function(err) {
        // Failsafe. Will log errors from all promises above. 
        console.log(err);
    });
});