2016-02-04 37 views
2

我的目標是遍歷文件目錄,對每個文件運行一些操作,並返回包含目錄子集的json對象。使用promise異步遍歷一組文件/ defer

我得到它在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

你是不是等待fs.stats打電話回來 –

+0

http://blog.slaks.net/2015-01-05/introducing-promises/#composing-promises – SLaks

+0

看起來這將是值得改變爲藍鳥。 – baao

回答

1

爲了讓您的上述代碼正常工作,您必須將promise用於其完整擴展。請參閱MDN或有關承諾如何詳細工作的類似來源。

鑑於此,您應該將fs.stat包裝在承諾中。這樣的承諾管理等待你的結果,並給你一個選擇,以更多的同步問題運行你的大部分代碼。

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); 
    }); 
}); 
+0

感謝這個偉大的示例@pichfl。一個問題:一旦q.all()返回它的數組並且promise鏈中的特定步驟解決了,那麼dir var就會超出範圍。該dir值在後續步驟中需要。任何意見,如何不失去這一點?我嘗試創建一個對象,將dir值存儲爲一個屬性,將promise作爲另一個存儲,但它不起作用。 – squeezebox

+1

@squeezebox我將我的答案擴展爲包含一個示例,您將如何訪問承諾鏈中的下游目錄數據。請仔細閱讀承諾如何更徹底地工作。請記住,您必須**返回一個承諾,將其結果沿着鏈條使用,否則它將以與您的對象一樣的未解決狀態結束。總是在另一個'.then()'回調中做這樣的轉換,以確保您有承諾結果。 – pichfl

+0

再次感謝@ pichfl。這是一個巨大的幫助。 – squeezebox