使用承诺和递归迭代文件目录

Posted

技术标签:

【中文标题】使用承诺和递归迭代文件目录【英文标题】:Iterating file directory with promises and recursion 【发布时间】:2016-11-13 19:24:23 【问题描述】:

我知道我将在以下函数中提前返回,如何将递归承诺链接到我的结果?

我的目标是获取目录及其所有子目录中的文件列表数组。数组是一维的,我在这个例子中使用concat。

function iterate(body) 
    return new Promise(function(resolve, reject)
        var list = [];
        fs.readdir(body.path, function(error, list)
            list.forEach(function(file)
                file = path.resolve(body.path, file);
                fs.stat(file, function(error, stat)
                    console.log(file, stat.isDirectory());
                    if(stat.isDirectory()) 
                        return iterate(path: file)
                            .then(function(result)
                                list.concat(result);
                            )
                            .catch(reject);
                     else 
                        list.push(file);
                    
                )
            );
            resolve(list);
        );
    );
;

【问题讨论】:

***.com/questions/29020722/… 阅读并修改“return” 你的功能的目标是什么?你想最终得到什么?具有完整路径且没有目录的所有文件的递归列表? @jfriend00 包含目录及其所有子目录中文件列表的任何数组。数组是一维的。 @user2727195 - 一如既往,请将您的代码目标添加到您的问题中。 好的,我已经按照我的目标进行了编辑 【参考方案1】:

您的代码中有许多错误。部分列表:

    .concat() 返回一个新数组,因此list.concat(result) 本身实际上并没有做任何事情。

    您正在同步调用resolve(),而不是等待所有异步操作完成。

    您正试图从多个嵌套异步回调的深处递归返回。你不能那样做。这不会在任何地方得到结果。

我发现使用fs 模块的承诺版本更容易使用。我使用 Bluebird 来创建它,然后你可以这样做:

const path = require('path');
var Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

function iterate(dir) 
    return fs.readdirAsync(dir).map(function(file) 
        file = path.resolve(dir, file);
        return fs.statAsync(file).then(function(stat) 
            if (stat.isDirectory()) 
                return iterate(file);
             else 
                return file;
            
        )
    ).then(function(results) 
        // flatten the array of arrays
        return Array.prototype.concat.apply([], results);
    );

注意:我将iterate() 更改为仅采用初始路径,因此它更通用。您最初可以将body.path 传递给它以适应。


这是一个使用通用 ES6 承诺的版本:

const path = require('path');
const fs = require('fs');

fs.readdirAsync = function(dir) 
    return new Promise(function(resolve, reject) 
        fs.readdir(dir, function(err, list) 
            if (err) 
                reject(err);
             else 
                resolve(list);
            
        );
    );


fs.statAsync = function(file) 
    return new Promise(function(resolve, reject) 
        fs.stat(file, function(err, stat) 
            if (err) 
                reject(err);
             else 
                resolve(stat);
            
        );
    );



function iterate2(dir) 
    return fs.readdirAsync(dir).then(function(list) 
        return Promise.all(list.map(function(file) 
            file = path.resolve(dir, file);
            return fs.statAsync(file).then(function(stat) 
                if (stat.isDirectory()) 
                    return iterate2(file);
                 else 
                    return file;
                
            );
        ));
    ).then(function(results) 
        // flatten the array of arrays
        return Array.prototype.concat.apply([], results);
    );


iterate2(".").then(function(results) 
    console.log(results);
);

这是一个添加了可自定义过滤功能的版本:

function iterate2(dir, filterFn) 
    // default filter function accepts all files
    filterFn = filterFn || function() return true;
    return fs.readdirAsync(dir).then(function(list) 
        return Promise.all(list.map(function(file) 
            file = path.resolve(dir, file);
            return fs.statAsync(file).then(function(stat) 
                if (stat.isDirectory()) 
                    return iterate2(file, filterFn);
                 else 
                    return filterFn(file)? file : "";
                
            );
        )).then(function(results) 
            return results.filter(function(f) 
                return !!f;
            );
        );
    ).then(function(results) 
        // flatten the array of arrays
        return Array.prototype.concat.apply([], results);
    );


// example usage
iterate2(".", function(f) 
    // filter out 
    return !(/(^|\/)\.[^\/\.]/g).test(f);
).then(function(results) 
    console.log(results);
);

【讨论】:

我不能使用 bluebird,这个项目是无依赖的并且使用原生的 Promise。 @user2727195 - 好吧,我建议您手动承诺您正在执行的fs 操作并使用它们,因为承诺是协调大量异步操作的方式。使用 node.js 而不使用任何外部模块有点傻。这是 node.js 的主要优势,因此您可以使用预先构建的模块来解决您遇到的问题,而不是重新发明***。除非这是家庭作业...我可以编写通用版本,但工作量要多得多。 @user2727195 - 我使用通用 node.js 添加了一个版本,没有附加库。 :) 至少你能给我一些工作方向,我会努力实现它。你甚至可以看到 zip,它是免费的,我使用了系统进程 zip。 ***.com/questions/38298466/…. @user2727195 - 我已经将它包含在我的答案中。随着 NPM 的出现以及在其他模块上指定依赖关系的便利性,我认为“无依赖关系”是一个愚蠢的概念。这只需要你重新发明其他人已经做过的事情,这是首先使用 node.js 的原因的 2/3(在我看来)。

以上是关于使用承诺和递归迭代文件目录的主要内容,如果未能解决你的问题,请参考以下文章

Java中递归复制文件夹及文件(简易版)

等待递归 readdir 函数结束

fs.readdir 递归搜索深度=1

使用java递归方法遍历指定目录下所有子目录和子文件

节点递归承诺永远不会退出

在Databricks(DBFS)中递归列出目录和子目录的文件