簡體   English   中英

Javascript-如何確保(遞歸)函數調用上的控制流

[英]Javascript - how to ensure control flow on (recursive) function calls

我已經編寫了此遞歸函數,它的行為完全不符合預期。 因此,在調試中,我發現JS不僅跳過了遞歸調用,還跳過了整個函數,並繼續執行,然后在認為合適時運行函數調用,從而弄亂了列表的整個順序。
我可以以某種方式使其不這樣做嗎? 我已對代碼進行了注釋,以進一步解釋。

listFileSystem: function () {
    var htmlString = '<ul id="file-system-list"';
    var addFileEntry = function (fs) {
        var reader = fs.createReader();
        reader.readEntries(
                function (entries) {
                    entries.forEach(function (entry) {
                        if (entry.isDirectory === true) {
                            htmlString +=
                                    '<li>'
                                    + '<h2>' + entry.fullPath + '</h2>'
                                    + '<ul>'
                                    ;
                            // here is the recursive call that's 'skipped'
                            // and performed some random time later
                            // resulting in all the recursive calls 
                            // returning in random order
                            addFileEntry(entry);
                            htmlString += '</ul></li>';
                        } else {
                            htmlString +=
                                    '<li><h3>' + entry.fullPath + "</h3></li>";
                        }
                    });
                }
        );
    };
    // this function too is skipped, and then performed later, resulting
    // in the <ul> being closed instantly and appended as such
    // (since the callback here calls the list-building function)
    // in Debugging however it seems like I can just wait until this
    // function has executed and control flow at this point then is sane
    window.resolveLocalFileSystemURL(cordova.file.applicationDirectory,
    addFileEntry);

    htmlString += '</ul>';
    $('#file-cont').append(htmlString);

}

遞歸和異步是開發人員的兩個很棒的工具,不幸的是,由於一個根本原因,它們彼此不喜歡:遞歸使用執行棧來創建返回對象的結構,而javascript的異步機制則有目的地讓執行的當前迭代堆棧在執行異步操作之前先運行其過程。 本質上,回調是執行堆棧幀的當前函數的返回地址的異步版本。 你明白我的意思嗎?

所以我們喜歡遞歸,因為執行引擎為我們處理了響應的結構,但是我們喜歡異步,因為文件系統很慢並且我們不想掛斷執行。

那么,如何將方形釘壓入圓孔? 我們可以保持一個假裝的遞歸級別,該遞歸級別將作為計數器來告訴我們何時遞歸完成。 這是我的看法,但這是針對NodeJS的:

const fs = require('fs');

//These keep track of the recursion.
var recurseLevel = 0;
var paths = [];

function AsyncRecurse(path) {
  recurseLevel += 1;
  fs.readdir(path, createReadDirCallback(path));
}

//Because the callback of fs.readdir doesn't include the state I need (the current path we're investigating) I have to create a callback and pass the path by closure.
function createReadDirCallback(path) {
  return function (err, files) {
    if (err) {
      throw 'RIP';
    }
    files.forEach(file => {
      const fullPath = path + '\\' + file;
      paths.push(fullPath);
      if (fs.statSync(fullPath).isDirectory()) {
        AsyncRecurse(fullPath);
      }
    });
    recurseLevel -= 1;
    if (recurseLevel == 0) {
      //Only when I know for a fact all the recursion is complete can I confidently work with the results. This is the root of your problem: you start working with the result of your recursion long before it is done.
      finalCallback(paths);
    }
  }
}

function finalCallback(paths) {
  var html = '<ul>';
  paths.sort();
  paths.forEach(path => {
    //Build your html here synchronously.
  });
  //Your html is ready!
  console.log(paths);
}

AsyncRecurse(somePath);

在這里,我們看到了遞歸的“最終返回地址”以及遞歸的當前級別。 本質上,我讓javascript一個接一個地運行每個異步調用,而我是手動執行遞歸位。

不幸的是,如果我們希望文件系統以最快的速度運行,那么我們將不得不用請求轟炸它,並以無組織的方式接收響應。 這就是為什么我必須在最后對所有響應進行排序。

您可以使用Promise和Promisify大大簡化此解決方案。 盡管這樣做很有趣,但實際上您正在做的是map-reduce ,所以我建議您閱讀並實現它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM