使用 Node FS 查找文件数量并获取总行数

Find amounts of files and get total line count with Node FS

我正在构建一个异步输出目录的文件数和行数的节点脚本;但是,我在使用它的异步控制流时遇到了麻烦。

// Import Dependencies
const fs = require('fs');

const get_dir_line_count = (dir) => {
  let output = { file_count: 0, file_line: 0, path: '' };
  new Promise( (resolve, reject) => {
    fs.readdir(dir, (err, dir_contents) => {
      resolve(dir_contents);
    });
  }).then( (promise_contents) => {
    Promise.all(promise_contents.map( (file) => {
      const file_path = dir + '/' + file;
      return new Promise( (resolve, reject) => {
        fs.stat(file_path, (err, stat) => {
          if(err || file[0] === '.') return err;
          if(stat.isDirectory() && file !== 'node_modules'){
            get_dir_line_count(file_path);
          }
          else if(stat.isFile()){
            promise_line_count(file_path)
              .then( (line_count) => {
                output.path = dir;
                output.file_line += line_count;
                output.file_count++;
                resolve(output);
              });
          };
        });
      }).then( (resolved_output) => {
        console.log(resolved_output)
        return resolved_output;
      });
    }));
  });

};

const promise_line_count = (pathToFile) => {
  let line_count = 0;
  return new Promise( (resolve, reject) => {
    fs.createReadStream(pathToFile)
      .on("data", (buffer) => {
        buffer.forEach( (chunk) => {
          if(chunk === 10) line_count++;
        });
      }).on("end", () => {
        resolve(line_count);
      });
  });
};

const directory = process.argv[2];
get_dir_line_count('./../' + directory);

我的意图是递归遍历输出 Promise.all 数组的目录。每个数组都是目录计算数据的集合。但是,我在 Promise.all 上遇到异步控制流问题。如果有人可以提供反馈,那将会很有帮助。

输出:

项目 = 5 个文件,50 行

Project/src = 10 个文件,60 行

Project/apple = 20 个文件,200 行

...等等

一个问题是您没有从 get_dir_line_count 函数本身 returning 任何东西:

const get_dir_line_count = (dir) => {
  let output = { file_count: 0, file_line: 0, path: '' };
  new Promise( (resolve, reject) => {
// ^---- missing a return statement

另一个问题是您忘记了 return 来自 Promise.all 的结果,因此可以正确构建链:

// ...
}).then( (promise_contents) => {
    Promise.all(promise_contents.map( (file) => {
// ^---- missing a return

您还忘记了 return(或解析)对 get_dir_line_count 的递归调用:

if(err || file[0] === '.') return err;
  if(stat.isDirectory() && file !== 'node_modules'){
    get_dir_line_count(file_path);
  // ^--- missing a return statement or resolve statement
  }

最后,由于您是 return 从 get_dir_line_count 输出对象,您可以通过添加 then 并将结果传递到 console.log 来检查事情是否正常:

const directory = process.argv[2];
get_dir_line_count('./../' + directory).then(console.log) // <-- get the output object and the log it

一般来说,就处理异步代码的复杂性而言,清理控制流可以做的主要事情是将单独的逻辑提取到单独的函数中。

您可以在下面找到一种方法的代码示例以及嵌入的注释(我还保留了带下划线的命名首选项):

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

// resolves with the file names within the given directory
function get_file_names(dir) {
  return new Promise((resolve, reject) => {
    fs.readdir(dir, (err, fileNames) => {
      if (err) return reject(err);
      resolve(fileNames);
    });
  });
}

// resolves with an object containing the type ('file' or 'dir') for the given file path and the file path itself: { file_path, type }
function get_path_and_type(file_path) {
  return new Promise((resolve, reject) => {
    fs.stat(file_path, (err, stat) => {
      if (err) return reject(err);
      if (!stat.isDirectory() && !stat.isFile()) reject('Invalid Type');
      const type = stat.isDirectory() ? 'dir' : 'file';
      resolve({
        file_path,
        type
      });
    });
  });
}

// same as before, counts lines for the given file path
function count_lines(file_path) {
  return new Promise((resolve, reject) => {
    let lineCount = 0;
    fs.createReadStream(file_path)
      .on("data", (buffer) => {
        buffer.forEach((chunk) => {
          if (chunk === 10) lineCount++;
        });
      }).on("end", () => {
        resolve(lineCount);
      }).on("error", reject);
  });
};

function get_dir_line_count(dir) {

  const output = {
    file_count: 0,
    file_lines: 0,
    path: dir
  };

  // get all filenames in the given directory
  return get_file_names(dir)
    // filter all file names that start with a '.' or include the string 'node_modules'
    .then((names) =>
      names.filter((name) =>
        !name.startsWith('.') && !name.includes('node_modules')
      )
    )
    // map every file name into a promise that resolves with the type for that file name within the given dir
    .then((names) =>
      names.map((name) =>
        get_path_and_type(path.join(dir, name))
        .catch(console.warn) // log invalid typed files if necessary
      )
    ).then((paths_and_types_promises) =>
      Promise.all(paths_and_types_promises.map((promise) =>
        promise.then(({
          file_path,
          type
        }) => {
          if (type === 'dir') {
            // if current file path corresponds to a directory
            // recursive count its files and lines and add it to the overall output
            return get_dir_line_count(file_path)
              .then((recursive_output) => {
                output.file_count += recursive_output.file_count;
                output.file_lines += recursive_output.file_count;
              });
          } else {
            // count the lines for the current file path and then update the overall output
            return count_lines(file_path)
              .then((file_lines) => {
                output.file_lines += file_lines;
                output.file_count += 1;
              })
          }
        })
      ))
    // this last chain makes sure we wait for the promise to resolve
    // and populate the output object before resolving with it
    ).then(() => output);
}

get_dir_line_count(process.argv[2])
  .then(console.log);
const fs = require('fs');
const path = require('path');
let output = {};
let lastDir = '';

const walk = (dir) => {

    return new Promise((resolve, reject) => {
        output[dir] = {
            files: 0,
            lines: 0,
            path: ''
        };

        fs.readdir(dir, (err, list) => {
            if (err) {
                return reject(err);
            }

            let pending = list.length;

            if (!pending) {
                return resolve(output);
            }

            list.forEach((file) => {
                file = path.resolve(dir, file);

                fs.stat(file, (err, stat) => {
                    if (stat && stat.isDirectory()) {
                        walk(file)
                            .then((res) => {
                                if (!--pending) {             
                                    resolve(output);
                                }
                            })
                    }
                    else {
                        let lc = 0;

                        fs.createReadStream(file)
                            .on('data', (buffer) => {
                                buffer.forEach((chunk) => {
                                    if (chunk === 10) {
                                        lc++;
                                    }
                                })
                            })
                            .on('end', () => {
                                output[dir].files++;
                                output[dir].lines += lc;
                                output[dir].path = dir;

                                if (!--pending) {
                                    resolve(output);
                                }
                            });
                    }
                })
            })
        })
    });
};

walk('.')
    .then(console.log)
    .catch(console.log);