如何使用promises递归地处理目录树

时间:2015-05-04 06:41:32

标签: javascript node.js promise q

我正在尝试编写一个函数来完成与使用带有promise模式的回调模式编写的相同的函数:

function readdirRecursive(path,handler,callback)  {
  var errs = [],
      tree = {};
  fs.readdir(path,function(err,dir)  {
    if(err)return callback(err);
    var pending = dir.length;
    if(!pending)return callback(null,tree);
    dir.forEach(function(file)  {
      var newPath = Path.join(path,file);
      fs.stat(newPath,function(err,stats)  {
        if(stats.isDirectory())  {
          readdirRecursive(newPath,handler,function(err,subtree)  {
            tree[file] = subtree
            handler(tree,newPath,file,"directory",function(err)  {
              if(err)errs.push(err);
              if(!--pending)return callback(errs.length>0?errs:null,tree);
            });
          });
        } else  {
          tree[file] = null; 
          handler(tree,newPath,file,"file",function(err)  {
            if(err)errs.push(err);
            if(!--pending)return callback(errs.length>0?errs:null,tree);
          });
        }
      });
    });
  });
};

这是我目前的尝试:

function readdirRecursive(path)  {
  var tree = {};
  return Q.Promise(function(resolve,reject,notify)  {
    return readdir(path)
    .then(function(dir)  {
      var futures = [];
      var pending = dir.length;
      if(!pending)return resolve(tree);
      dir.forEach(function(file)  {

        var deferred = Q.defer();
        var subPath = Path.join(path,file);
        futures.push(stat(subPath)
        .then(function(stats)  {
          if(stats.isDirectory())  {
            tree[file] = tree;
            var sub = readdirRecursive(subPath)
            sub
            .then(function(subtree)  {
              notify({
                path:subPath,
                name:file,
                type:"directory",
                done:deferred,
                pending:pending
              });
              //return subtree;
            },reject,notify);
          } else  {
            tree[file] = null;
            notify({
              tree:tree,
              path:subPath,
              name:file,
              type:"file",
              done:deferred,
              pending:pending
            });
            //return null;
          }
          //console.log("tree",tree);
          deferred.promise()
          .then(function()  {
            console.log("pending promise");
            if(!--pending)resolve(tree);
          }
          ,function(err)  {
            reject();
          });
        }));
      });
      return Q.all(futures)
      .then(function(futures)  {
        console.log("hi",futures);
      });
    });
  });
};

此代码将遍历整个树,但它不会返回树,并且会发生通知操作,但延迟的promise不会解析。

在通知事件之前启动延迟承诺时,根本不会发生任何事情。

我知道我可以通过将done函数交给progress事件来解决这个问题,而不是试图给出某种类型的承诺,但是我想在这里充分利用promises,例如,代码完全符合我的要求:

function readdirRecursive(path)  {
  var tree = {};
  return Q.Promise(function(resolve,reject,notify)  {
    return readdir(path)
    .then(function(dir)  {
      var futures = [];
      var pending = dir.length;
      if(!pending)return resolve(tree);
      dir.forEach(function(file)  {

        var deferred = Q.defer();
        var subPath = Path.join(path,file);
        console.log("file",file);
        /*deferred.promise()
        .then(function()  {
          console.log("pending promise");
          if(!--pending)resolve(tree);
        }
        ,function(err)  {
          reject();
        });*/
        futures.push(stat(subPath)
        .then(function(stats)  {
          if(stats.isDirectory())  {
            var sub = readdirRecursive(subPath)
            sub
            .then(function(subtree)  {
              tree[file] = subtree
              notify({
                path:subPath,
                name:file,
                type:"directory",
                done:function(err)  {
                  console.log("pending promise");
                  if(err)return reject(err);
                  if(!--pending)resolve(tree);
                },
                pending:pending
              });
              //return subtree;
            },reject,notify);
          } else  {
            tree[file] = null;
            notify({
              tree:tree,
              path:subPath,
              name:file,
              type:"file",
              done:function(err)  {
                console.log("pending promise");
                if(err)return reject();
                if(!--pending)resolve(tree);
              },
              pending:pending
            });
            //return null;
          }
          //console.log("tree",tree);
        }));
      });
      return Q.all(futures)
      .then(function(futures)  {
        console.log("hi",futures);
      });
    });
  });
};

这是将执行这些功能的代码:

readdirRecursive("../").then(function(tree)  {
  console.log("TREE!!!",tree);
},function(err)  {
  console.log("ERROR",err);
},function(progress)  {
  console.log("PRGRESS WAS MADE",progress);
  progress.done();
});

1 个答案:

答案 0 :(得分:1)

我的第一个想法是简单地将原始函数包装在一个承诺中。这通常是我在不重新设计底层代码的情况下执行此操作的方式:

function readdirRecursiveWithPromise (path, handler) {
    return new Promise((resolve, reject) => {
        readdirRecursive(path, handler, (err, tree) => {
            if (err) {
                reject(err);
            }
            else {
                resolve(tree);
            }
        });
    })
}

不幸的是,当我尝试测试此代码时,我发现了一些与您的代码相关的潜在问题。

首先,我不知道你的'处理程序'应该做的。您尚未提供此说明或描述它应该做什么。它对问题至关重要,因为它控制着最终的回调是否最终被调用,所以我可以推测出这个“处理程序”是什么?控制此操作以及您的回调是否可以控制此操作。没有被称为可能是由于你的“处理程序”中的逻辑。

接下来的问题是你的待定'变量总共被设置为文件目录的数量,但它仅针对目录递减。所以你的待定'变量永远不会达到0,并且永远不会调用调用回调的条件代码。

所以我要摆脱'处理程序'和'待定'而且我会告诉你我是如何用从头开始的承诺改写的。

以下是完整的工作代码示例:https://github.com/ashleydavis/read-directory-with-promises。继续阅读以获得解释。

让我们从基于承诺的readdir版本开始,递归:

function readdir (path) { // Promise-based version of readdir.
    return new Promise((resolve, reject) => { // Wrap the underlying operation in a promise.
        fs.readdir(path, (err, files) => {
            if (err) {
                reject(err); // On error, reject the promise.
            }
            else {
                resolve(files); // On success, resolve the promise.
            }
        });    
    });
};

我们还需要一个基于promise的函数,我们可以用它来确定特定路径的类型(文件或目录):

function determineType (parentPath, childPath) { // Promise-based function to determine if the path is a file or directory.
    return new Promise((resolve, reject) => {
        fs.stat(path.join(parentPath, childPath), (err, stats) => {
            if (err) {
                reject(err);
            }
            else {
                resolve({ 
                    path: childPath,
                    type: stats.isDirectory() ? 'directory' : 'file' // Check if it's a directory or a file.
                }); 
            }
        });
    });
};

现在我们可以扩展determineType并创建一个函数,获取一系列路径并确定每个路径的类型。这使用Promise.all并行执行多个异步操作:

function determineTypes (parentPath, paths) { // Async function to determine if child paths are directories or files.

    return Promise.all(
            paths.map(
                childPath => determineType(parentPath, childPath) // Is the path a directory or a file?
            )
        );
};

现在我们可以构建基于承诺的重新版readdir

function readdirTree (rootPath) { // Read an entire directory tree, the promise-based recursive version.
    return readdir(rootPath) // Initial non-recursive directory read.
        .then(childPaths => determineTypes(rootPath, childPaths)) // Figure out the type of child paths.
        .then(children => {
            return Promise.all(children // Use Promise.all to figure out all sub-trees in a parallel.
                .filter(child => child.type === 'directory') // Filter so we only directories are remaining.
                .map(child => {
                    return readdirTree(path.join(rootPath, child.path)) // It's a directory, recurse to the next level down.
                        .then(subTree => {
                            return {
                                path: child.path,
                                subTree: subTree,
                            };
                        });
                })
            );
        })
        .then(children => {
            const tree = {}; // Reorganise the list of directories into a tree.
            children.forEach(directory => {
                tree[directory.path] = directory.subTree;
            });
            return tree;
        });
};

以下是一个使用示例:

readdirTree("c:\\some-directory")
    .then(tree => {
        console.log("tree:");
        console.log(tree);
    })
    .catch(err => {
        console.error("error:");
        console.error(err);
    });

我已经在我的Github上找到了一个完整的工作示例:https://github.com/ashleydavis/read-directory-with-promises

希望它可以帮助你前进。