简体   繁体   English

在Node.js中混合承诺和递归

[英]Mixing Promises and Recursion in Node.js

I have a sticky problem I am trying to solve. 我有一个棘手的问题要解决。 To illustrate the problem, I will use a familiar scenario: traversing a directory. 为了说明这个问题,我将使用一个熟悉的场景:遍历目录。 I know there are tons of libraries out that that already traverse a directory. 我知道有很多图书馆已经遍历了目录。 However, that's not what I'm trying to do. 但是,这不是我要尝试的。 Traversing a directory is just a metaphor for my problem. 遍历目录只是我的问题的隐喻。

Basically, I have the following: 基本上,我有以下几点:

structure: [],

traverseDirectory: function(path) {
  var scope = this;

  var promise = new Promise(function(resolve, reject) {
    openDirectory(path, function(results) {
      for (var i=0; i<results.length; i++) {
        if (results[i].type === 'directory') {
          scope.traverseDirectory(results[i].name);
        } else {
          scope.structure.push({ filename:name });
        }
      }
      resolve(scope.structure);
    });
  });
  return promise;
},

getDirectoryStructure: function(path) {
  this.traverseDirectory(path)
    .then(function(results) {
      // Print out all of the files found in the directories.
      console.log(JSON.stringify(results));
    }
  ;
}

My problem is the .then of getDirectoryStructure fires before the directory is actually traversed. 我的问题是.thengetDirectoryStructure火灾前的目录实际通过。 Its not waiting like I thought it would. 它没有像我想的那样等待。 Plus, I'm not sure how to "pass" (not sure if that's the right word) the promise around as I'm recursing through the directory structure. 另外,在遍历目录结构时,我不确定如何“传递”(不确定这是否正确)诺言。 Can I even do what I'm trying with promises? 我什至可以兑现承诺吗?

Thank you for any help. 感谢您的任何帮助。

In this case you would need to consider that you have multiple "steps" per level... or in your directory traversal example multiple sub directories, so essentially you need to fork... what @Anonymous0day suggests is close, however returning out of the for loop is counter indicative. 在这种情况下,您需要考虑每个级别有多个“步骤” ...或在目录遍历示例中有多个子目录,因此从本质上讲您需要分叉... @ Anonymous0day的建议很接近,但是返回for循环是反指示的。

What you need is Promise.all: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all 您需要的是Promise.all: https//developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

var traverseDirectory = function(path) {

  var promise = new Promise(function(resolve, reject) {
    openDirectory(path, function(results) {
      resolve(results);
    });
  });
  return promise.then(function(results) {
    var pros = [];

    for (var i=0; i<results.length; i++) {
      if (results[i].type === 'directory') {
        pros.push(scope.traverseDirectory(results[i].name)); // <- recursive call
      } else {
        pros.push([{filename:name}]);
      }
    }

    return Promise.all(pros).then(function(arrs) {
       var structure = [];
       for (var i=0; i<arrs.length; i++)
         structure = structure.concat(arr[i]);
       return structure;
    });
  });
}

(PS I kinda de "scoped" this, to show you that you don't need an external object in the same way to keep track of the structure... you can keep it inside the function and only expose it when the outer promise resolves). (PS我有点“窥视”了这一点,以表明您不需要以相同的方式跟踪结构的外部对象……您可以将其保留在函数中,并且仅在外部promise时公开它解决)。

But the biggest thing you needed to do was actually WAIT to resolve the outer promise until after you're all done traversing (PS - I'll leave it to you to see what Promise.all does if the 'pros' array is empty). 但是,您需要做的最大的事情实际上是等待解决外部承诺,直到遍历完所有遍历为止(PS-我将留给您看看Promise.all,如果“ pros”数组为空) 。

You were seeing it execute immediately because it was literally resolving right after it was done with the for loop... if those recursions had actually been asynch, the event loop would indeed immediately resolve. 您正在看到它立即执行,因为它实际上是在使用for循环完成后立即解决的……如果这些递归实际上是在异步的,那么事件循环的确会立即解决。

Cheers, lemme know if that makes sense. 干杯,莱姆知道这是否有意义。 [EDITED for proper Promise.all().then(success, fail) instead of the .catch I had]. [为正确的Promise.all()。then(成功,失败)编辑,而不是我拥有的.catch]。

Here is a relatively concise way to do it that avoids for-loops and mutating variables. 这是一种相对简洁的方法,可以避免for循环和变量变异。 It returns a tree structure of all the retrieved results: 它返回所有检索到的结果的树形结构:

// returns a promise for an object with two properties:
//   directoryname (string)
//   contents (array of objects (directories and files) for the contents of the directory)
function traverseDirectory(path) {
    return new Promise(function(resolve, reject) {
        openDirectory(path, resolve);
    }).then(function (results) {
         return Promise.all(results.map(function (item) {
             return item.type === 'directory'
                 ? traverseDirectory(item.name)
                 : { filename: item.name };
        }));
    }).then(function (contents) {
       return {
           directoryname: path,
           contents: contents
       };
    });
}

If your objective is to obtain a flat array of all files in the directory tree, you can do this (everything is the same except for the last then ): 如果您的目标是获取目录树中所有文件的平面数组,则可以执行此操作(除了最后一个then是,其他都一样):

// returns a promise for an array of all of the files in the directory and its descendants
function traverseDirectory(path) {
    return new Promise(function(resolve, reject) {
        openDirectory(path, resolve);
    }).then(function (results) {
        return Promise.all(results.map(function (item) {
            return item.type === 'directory'
                ? traverseDirectory(item.name)
                : { filename: item.name };
        }));
    }).then(function (contents) {
        return Array.prototype.concat.apply([], contents);
    });
}

For a more flexible approach, you might choose for : 对于更灵活的方法,您可以选择:

  • .getDirectoryStructure() to deliver what it says, a representation of a directory hierarchy. .getDirectoryStructure()传递其内容,表示目录层次结构。
  • A flattener to operate on the hierarchical array to produce what is actually asked for - a flat array of objects. 用于在分层数组上进行操作以产生实际要求的展平器-对象的平坦阵列。 You can use something like lodash's _.flattenDeep() or write your own. 您可以使用lodash的_.flattenDeep()之类的东西,也可以编写自己的东西。

First, a couple of general points : 首先,有几点要点:

  • In general, you should promisify at the lowest possible level. 通常,您应该在最低级别上承诺。 In this case, that means promisifying a version of openDirectory() . 在这种情况下,这意味着分配openDirectory()的版本。 By doing so the code in traverseDirectory() will simplify. 这样, traverseDirectory()的代码将得到简化。 (In the solution below, what remains of traverseDirectory() is actually subsumed by getDirectoryStructure() ). (在下面的解决方案中, traverseDirectory()剩余内容实际上由getDirectoryStructure()包含)。
  • Use Promise.all() to aggregate families of promises generated during the traversal. 使用Promise.all()汇总遍历过程中生成的promise系列。

In doing this, you can dispense with outer var structure and rely instead on Promise.all() to deliver, (recursively) an array of results. 在此过程中,您可以免除外部var structure而依赖于Promise.all()传递(递归)结果数组。

Here's the code : 这是代码:

var dirOpener = {
    openDirectoryAsync: function(path) {
        return new Promise(function(resolve, reject) {
            openDirectory(path, resolve);
        });
    },
    getDirectoryStructure: function(path) {
        var scope = this;
        return scope.openDirectoryAsync(path).then(function(results) {
            var promises = results.map(function(file) {
                return (file.type === 'directory') ? scope.getDirectoryStructure(file.name) : { filename: file.name };
            });
            return Promise.all(promises);
        });
    },
    flattenDeep: function(arr) {
        var fn = arguments.callee;
        return arr.reduce(function(a, x) {
            return a.concat(Array.isArray(x) ? fn(x) : x);
        }, []);
    }
}

For an array reflecting the full directory structure, call as follows : 对于反映完整目录结构的数组,请如下调用:

dirOpener.getDirectoryStructure(rootPath)
.then(function(results) {
    console.log(results);
})
.catch(function(e) {
    console.log(e);
});

Or, for a flattened array containing just the filename objects : 或者,对于仅包含文件名对象的扁平数组:

dirOpener.getDirectoryStructure(rootPath)
.then(dirOpener.flattenDeep)
.then(function(results) {
    console.log(results);
})
.catch(function(e) {
    console.log(e);
});

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

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