【问题标题】:how to handle reading a directory tree recursively using promises如何使用 Promise 递归地读取目录树
【发布时间】:2015-07-13 11:28:35
【问题描述】:

我正在尝试编写一个函数,该函数与以下使用带有承诺模式的回调模式编写的函数相同:

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

此代码将遍历整个树,但不会返回树,并且会发生通知操作,但延迟的承诺永远不会解决。

当延迟承诺在通知事件之前启动时,什么都不会发生。

我知道我可以通过将 done 函数交给进度事件而不是尝试给出某种承诺来解决这个问题,但我想在这里尽可能充分地使用承诺,例如,这个代码完全符合我的要求:

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

【问题讨论】:

  • 在 bluebird wiki 中有一个例子。
  • 我正在阅读目录,如果我想将它们读入树中,我可以这样做,但我想将它们读入树中,然后使用通知进行一些额外的处理事件,关于他们每个人。找到你的例子:link 我也必须使用 Q,因此是标签。
  • progress 已弃用
  • 那么最好以不同的方式处理它,尽管我对 node/js 相当熟练,但我对使用 Promise 完全陌生。我已经用“最终目标”的一些工作示例更新了代码,删除了一个承诺,但是考虑到这一点,我现在真的不知道该怎么做,因为通知操作是我在这里追求的功能类型.
  • 作为 Stack Overflow 中最响亮的承诺倡导者之一 - 我不会在这里使用它们 - 你想流式传输多个结果而不是单个 - 如果你想流式传输它们,我会使用 Observable如果你想使用 Promise,或者将它们推入一个数组,然后 .all 数组。

标签: javascript node.js promise q


【解决方案1】:

我的第一个想法是简单地将您的原始功能包装在一个承诺中。这通常是我在不重新设计底层代码的情况下这样做的方式:

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

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

首先,我不知道您的“处理程序”应该做什么。您没有对此提供解释或描述它应该做什么。这对问题很重要,因为它控制最终是否调用最终回调,所以我可以推测“处理程序”控制着这个操作,如果你的“回调”没有被调用,那可能是由于逻辑在您的“处理程序”中。

下一个问题是您的“待定”变量设置为文件目录的总数,但它仅对目录递减。因此,您的“待处理”变量将永远不会达到 0,并且您调用回调的条件代码将永远不会被调用。

因此,我将摆脱“处理程序”和“待处理”,我将向您展示如何从头开始使用 Promise 重写它。

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

让我们从基于 promise 的 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?
            )
        );
};

现在我们可以构建基于 Promise 的递归版本 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

希望它能帮助你前进。

【讨论】:

    猜你喜欢
    • 2018-08-12
    • 2021-01-29
    • 1970-01-01
    • 2011-01-29
    • 1970-01-01
    • 2022-12-30
    • 2015-08-26
    • 2014-09-22
    • 1970-01-01
    相关资源
    最近更新 更多