Node.js fs.readdir 递归目录搜索

新手上路,请多包涵

关于使用 fs.readdir 进行异步目录搜索的任何想法?我意识到我们可以引入递归并使用下一个要读取的目录调用读取目录函数,但我有点担心它不是异步的……

有任何想法吗?我看过 node-walk 很棒,但它不像 readdir 那样只给我数组中的文件。虽然

寻找像…这样的输出

['file1.txt', 'file2.txt', 'dir/file3.txt']

原文由 crawf 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

基本上有两种方法可以实现这一点。在异步环境中,您会注意到有两种循环:串行和并行。串行循环在进入下一次迭代之前等待一次迭代完成 - 这保证了循环的每次迭代都按顺序完成。在并行循环中,所有的迭代都是同时开始的,并且一个迭代可能在另一个之前完成,但是它比串行循环快得多。因此,在这种情况下,使用并行循环可能会更好,因为步行完成的顺序并不重要,只要它完成并返回结果(除非您希望它们按顺序排列)。

并行循环如下所示:

 var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var pending = list.length;
    if (!pending) return done(null, results);
    list.forEach(function(file) {
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            if (!--pending) done(null, results);
          });
        } else {
          results.push(file);
          if (!--pending) done(null, results);
        }
      });
    });
  });
};

串行循环如下所示:

 var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          results.push(file);
          next();
        }
      });
    })();
  });
};

并在你的主目录上测试它(警告:如果你的主目录中有很多东西,结果列表会很大):

 walk(process.env.HOME, function(err, results) {
  if (err) throw err;
  console.log(results);
});

编辑:改进的例子。

原文由 chjj 发布,翻译遵循 CC BY-SA 4.0 许可协议

这一个使用了节点 8 中可用的最大数量的新的、流行的功能,包括 Promises、util/promisify、解构、async-await、map+reduce 等等,让你的同事在试图弄清楚什么时挠头正在进行。

节点 8+

没有外部依赖性。

 const { promisify } = require('util');
const { resolve } = require('path');
const fs = require('fs');
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);

async function getFiles(dir) {
  const subdirs = await readdir(dir);
  const files = await Promise.all(subdirs.map(async (subdir) => {
    const res = resolve(dir, subdir);
    return (await stat(res)).isDirectory() ? getFiles(res) : res;
  }));
  return files.reduce((a, f) => a.concat(f), []);
}

用法

getFiles(__dirname)
  .then(files => console.log(files))
  .catch(e => console.error(e));

节点 10.10+

针对节点 10+ 进行了更新,具有更多神通:

 const { resolve } = require('path');
const { readdir } = require('fs').promises;

async function getFiles(dir) {
  const dirents = await readdir(dir, { withFileTypes: true });
  const files = await Promise.all(dirents.map((dirent) => {
    const res = resolve(dir, dirent.name);
    return dirent.isDirectory() ? getFiles(res) : res;
  }));
  return Array.prototype.concat(...files);
}

请注意,从节点 11.15.0 开始,您可以使用 files.flat() 而不是 Array.prototype.concat(...files) 来展平文件数组。

节点 11+

如果你想让每个人都大吃一惊,你可以使用以下使用 异步迭代器 的版本。除了非常酷之外,它还允许消费者一次提取一个结果,使其更适合于非常大的目录。

 const { resolve } = require('path');
const { readdir } = require('fs').promises;

async function* getFiles(dir) {
  const dirents = await readdir(dir, { withFileTypes: true });
  for (const dirent of dirents) {
    const res = resolve(dir, dirent.name);
    if (dirent.isDirectory()) {
      yield* getFiles(res);
    } else {
      yield res;
    }
  }
}

用法已更改,因为返回类型现在是异步迭代器而不是承诺

;(async () => {
  for await (const f of getFiles('.')) {
    console.log(f);
  }
})()

如果有人感兴趣,我在这里写了更多关于异步迭代器的文章: https ://qwtel.com/posts/software/async-generators-in-the-wild/

原文由 qwtel 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题