在Node.js中混合Promise和Recursion

时间:2015-10-29 02:03:46

标签: javascript node.js promise

我想解决一个棘手的问题。为了说明这个问题,我将使用一个熟悉的场景:遍历目录。我知道有很多库已经遍历了一个目录。但是,这不是我想要做的。遍历目录只是我问题的一个隐喻。

基本上,我有以下内容:

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

我的问题是在实际遍历目录之前.then getDirectoryStructure个{fire}。它不像我想象的那样等待。另外,我不知道如何通过目录结构来“传递”(不确定这是否是正确的词)的承诺。我甚至可以做我正在尝试承诺的事情吗?

感谢您的帮助。

3 个答案:

答案 0 :(得分:1)

在这种情况下,您需要考虑每个级别有多个“步骤”...或者在您的目录遍历示例多个子目录中,所以基本上您需要分叉... @ Anonymous0day建议接近,但是退出for循环是反指示。

你需要的是Promise.all:https://developer.mozilla.org/en-US/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我有点“限制”这个,向你展示你不需要一个外部对象以同样的方式来跟踪结构...你可以将它保留在函数内部并且只在它暴露时外部的承诺解决了。)

但是你需要做的最重要的事情就是实际上等待解决外部承诺,直到你完成遍历(PS - 我会留给你看看Promise.all如果'pros'数组做什么是空的。

你看到它立即执行,因为它在完成for循环之后就完全解决了......如果那些递归实际上是异步的,那么事件循环确实会立即解决。

干杯,lemme知道这是否有意义。 [编辑正确的Promise.all()。然后(成功,失败)而不是我有的.catch。

答案 1 :(得分:1)

这是一种相对简洁的方法,可以避免for循环和变异变量。它返回所有检索结果的树结构:

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

如果您的目标是获取目录树中所有文件的平面数组,则可以执行此操作(除最后一个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);
    });
}

答案 2 :(得分:1)

对于更灵活的方法,您可以选择:

  • .getDirectoryStructure()提供它所说的内容,即目录层次结构的表示。
  • 在分层数组上操作的flattener,用于生成实际需要的内容 - 平面对象数组。你可以使用lodash的_.flattenDeep()之类的东西,也可以自己编写。

首先,有几点要点:

  • 一般来说,你应该在尽可能低的水平上进行宣传。在这种情况下,这意味着宣传openDirectory()的版本。通过这样做,traverseDirectory()中的代码将简化。 (在下面的解决方案中,traverseDirectory()的剩余部分实际上归入getDirectoryStructure())。
  • 使用Promise.all()汇总遍历期间生成的承诺系列。

在执行此操作时,您可以省去外部变量structure,而是依赖Promise.all()来递送(递归)一系列结果。

以下是代码:

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);
        }, []);
    }
}

对于反映完整目录结构的数组,请调用如下:

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

或者,对于仅包含文件名对象的扁平数组:

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