简体   繁体   English

Javascript-如何确保(递归)函数调用上的控制流

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

I have written this recursive function and it did not at all behave like expected. 我已经编写了此递归函数,它的行为完全不符合预期。 So in debugging I found out that JS skips not only over the recursive calls but also over the function entirely and continues execution, then runs the function calls when it sees fit, thereby messing up the entire order of the list. 因此,在调试中,我发现JS不仅跳过了递归调用,还跳过了整个函数,并继续执行,然后在认为合适时运行函数调用,从而弄乱了列表的整个顺序。
Can I somehow make it not do that? 我可以以某种方式使其不这样做吗? I have commented the code for further explanation. 我已对代码进行了注释,以进一步解释。

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);

}

Recursion and asynchrony are two great tools of a developer, unfortunately they don't like each other for a fundamental reason: recursion uses the execution stack to create the structure of the returned object, whereas javascript's asynchronous mechanism purposefully lets the current iteration of the execution stack run its course before it executes the asynchronous operation. 递归和异步是开发人员的两个很棒的工具,不幸的是,由于一个根本原因,它们彼此不喜欢:递归使用执行栈来创建返回对象的结构,而javascript的异步机制则有目的地让执行的当前迭代堆栈在执行异步操作之前先运行其过程。 In essence, a callback is the asynchronous version of a return address of an execution stack frame's current function. 本质上,回调是执行堆栈帧的当前函数的返回地址的异步版本。 You see what I mean? 你明白我的意思吗?

So we like recursion because the execution engines takes care of the structure of our response for us, but we like asynchrony because the filesystem is slow and we don't want to hang up execution. 所以我们喜欢递归,因为执行引擎为我们处理了响应的结构,但是我们喜欢异步,因为文件系统很慢并且我们不想挂断执行。

So how do we force a square peg into a round hole? 那么,如何将方形钉压入圆孔? We can keep a pretend recursion level that will act as the counter that tells us when the recursion is complete. 我们可以保持一个假装的递归级别,该递归级别将作为计数器来告诉我们何时递归完成。 Here's my take on it, but it's for NodeJS: 这是我的看法,但这是针对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);

Here we see that I keep track of the 'final return address' of the recursion and also the current level of recursion. 在这里,我们看到了递归的“最终返回地址”以及递归的当前级别。 Essentially I'm manually doing the recursion bits while I let javascript run each asynchronous call one after the other. 本质上,我让javascript一个接一个地运行每个异步调用,而我是手动执行递归位。

Unfortunately, if we want the filesystem to act as fast as it can, then we're gonna have to bombard it with requests and receive the response in an unorganized way. 不幸的是,如果我们希望文件系统以最快的速度运行,那么我们将不得不用请求轰炸它,并以无组织的方式接收响应。 This is why I have to sort all the responses at the end. 这就是为什么我必须在最后对所有响应进行排序。

You can probably simplify this solution a lot using promises and promisify. 您可以使用Promise和Promisify大大简化此解决方案。 Although this is pretty fun to do, in essence what you are doing is map-reduce , so I suggest reading up and implementing that instead. 尽管这样做很有趣,但实际上您正在做的是map-reduce ,所以我建议您阅读并实现它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM