【问题标题】:asynchronously iterate over a group of files using promises/defer使用 promises/defer 异步迭代一组文件
【发布时间】:2016-05-14 19:42:31
【问题描述】:

我的目标是遍历文件目录,对每个文件运行一些操作,并返回一个包含目录子集的 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);
  }
});

【问题讨论】:

标签: javascript node.js express promise


【解决方案1】:

要使上述代码正常工作,您必须充分利用 Promise。请参阅MDN 或类似来源了解 Promise 的详细工作原理。

鉴于此,您也应该将 fs.stat 包装在一个 Promise 中。这样,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);
    });
});

【讨论】:

  • 感谢@pichfl 这个很好的例子。一个问题:一旦 q.all() 返回它的数组并且承诺链中的特定步骤解决了,dir var 就会超出范围。在后续步骤中需要该 dir 值。关于如何不失去它的任何建议?我尝试创建一个对象,将 dir 值存储为一个属性,将 promise 存储为另一个,但没有成功。
  • @squeezebox 我扩展了我的答案,包括一个示例,您将如何到达承诺链下游的目录数据。请阅读承诺如何更彻底地工作。请记住,您必须返回一个承诺,以便在整个链条中使用它的结果,否则它将像您的对象一样最终处于未解决状态。始终在另一个 .then() 回调中进行此类转换,以确保您获得承诺结果。
  • 再次感谢@pichfl。这是一个巨大的帮助。
猜你喜欢
  • 2023-01-16
  • 2016-07-10
  • 1970-01-01
  • 2021-08-27
  • 2014-08-28
  • 1970-01-01
  • 1970-01-01
  • 2017-11-15
相关资源
最近更新 更多