使用 Promise 和递归迭代文件目录

3
我知道我在下面的函数中提前返回了,如何将递归的 promises 链接到我的结果中?
我的目标是获取一个包含目录及其所有子目录中文件列表的数组。该数组是单维度的,在此示例中使用 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);
        });
    });
};

阅读这篇文章并修改“返回”。https://dev59.com/UF4b5IYBdhLWcg3whB-V - Robert Rowntree
你的函数的目标是什么?你最终想要得到什么?是一个递归列表,包含所有文件的完整路径而没有目录吗? - jfriend00
@jfriend00 返回包含目录及其所有子目录中文件列表的数组。该数组为单维数组。 - user2727195
@user2727195 - 请像往常一样,在您的问题中添加代码的目标。 - jfriend00
好的,我已经根据我的目标进行了编辑。 - user2727195
1个回答

12

你的代码存在许多错误,以下是部分列表:

  1. .concat() 返回一个新数组,所以仅使用 list.concat(result) 并不能实际执行任何操作。

  2. 你在同步调用 resolve() ,但没有等待所有异步操作完成。

  3. 你试图从多个嵌套的异步回调深处递归返回。这是不可能的,无法将结果传递到任何地方。

我认为使用 fs 模块的 promise 版本更加易用。我使用 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 Promise的版本:
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
@user2727195 - 我建议您手动将正在执行的fs操作转换为Promise并使用它们,因为Promise是协调大量异步操作的最佳方式。在使用node.js时不使用任何外部模块有点傻。这是node.js的主要优势,因此您可以使用预构建的模块来解决您遇到的问题,而不是重新发明轮子。除非这是作业...我可以制作一个通用版本,但这需要更多的工作。 - jfriend00
@user2727195 - 我添加了一个使用通用的node.js和没有附加库的版本。 - jfriend00
使用NPM,您可以指定外部模块的特定版本依赖关系,并仅在选择升级该模块时才升级。它会冻结直到您更改要包含的版本号为止。在我的看法中,在node.js编程中禁止使用外部模块是非常低效的。有成千上万个预构建模块已经解决了无数的问题。为什么要重新构建所有这些东西呢?算了吧,这是离题。我给你提供了冗长版本,重写了其他人已经为我们完成的工作。 - jfriend00
或者我应该在总结果的末尾进行隐藏文件的过滤? - user2727195
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接