Жду итеративную функцию без разделителя в JS - PullRequest
0 голосов
/ 18 марта 2019

У меня есть каталог с неизвестным количеством подпапок. Каждая подпапка может иметь или не иметь дополнительные подпапки. Я использую рекурсивную функцию через них. Из-за неизвестного количества вложенных папок мне не хватает способа убедиться, что все папки были проверены, прежде чем я продолжу. Мои знания об асинхронности и ожидании достаточно ограничены. Есть ли способ справиться с этой проблемой?

function searchForPackage(directory){
    fs.readdir(directory, function(err, files){
        if(err){
            return;
        }else{
            files.forEach(file => {
                var currentLocation = directory + "/" + file;
                if(fs.statSync(currentLocation).isDirectory() && file != 'bin' && file != '.bin'){
                    searchForPackage(currentLocation);
                    return;
                }else if(file == "package.json"){
                    var content = fs.readFileSync(currentLocation);
                    var jsonContent = JSON.parse(content);
                    var obj = {
                        name: jsonContent.name,
                        license: jsonContent.license,
                        version: jsonContent.version
                    }
                    jsonTable.push(obj);
                    jsonTable.push({name: jsonContent.name, license: jsonContent.license, version: jsonContent.version});
                    return;
                }
            })
        }
    })
  }

Ответы [ 2 ]

0 голосов
/ 18 марта 2019

Я бы предложил строить меньшие функции с изолированными задачами.Начните с функции files, которая просто возвращает все файлы и файлы всех подкаталогов -

const { readdir, stat } =
  require ("fs") .promises

const { join } =
  require ("path")

const files = async (path = ".") =>
  (await stat (path)) .isDirectory ()
    ? Promise
        .all
          ( (await readdir (path))
              .map (f => files (join (path, f)))
          )
        .then
          ( results =>
             [] .concat (...results)
          )
    : [ path ]

files () .then (console.log, console.error)

// [ './.main.js'
// , './node_modules/anotherpackage/README.md'
// , './node_modules/anotherpackage/package.json'
// , './node_modules/anotherpackage/index.js'
// , './node_modules/somepackage/.npmignore'
// , './node_modules/somepackage/LICENSE'
// , './node_modules/somepackage/README.md'
// , './node_modules/somepackage/package.json'
// , './node_modules/somepackage/index.js'
// , './node_modules/somepackage/test/test.js'
// , './package.json'
// ]

Затем создайте функцию search, которая зависит от files и добавляет возможность фильтровать результаты-

const { basename } =
  require ("path")

const search = async (query, path = ".") =>
  (await files (path))
    .filter (x => basename (x) === query)

search ("package.json", ".")
  .then (console.log, console.error)

// [ './node_modules/anotherpackage/package.json'
// , './node_modules/somepackage/package.json'
// , './package.json'
// ]

Затем выполните readPackages функцию, которая зависит от search и добавляет возможность чтения / анализа пакетов -

const { readFile } =
  require ("fs") .promises

const readPackages = async (path = ".") =>
  Promise
    .all
      ( (await search ("package.json", path))
          .map (package => readFile (package))
      )
    .then
      ( buffers =>
          buffers .map (b => JSON .parse (String (b)))
      )

readPackages ('.')
  .then (console.log, console.error)

// [ <contents of anotherpackage/package.json>
// , <contents of somepackage/package.json>
// , <contents of package.json>
// ]

Наконец, обратите внимание, как jsonTableбольше не глобальный.Вместо этого все данные аккуратно содержатся и проходят через нашу последовательность обещаний.


Если вы хотите преобразовывать пакеты по мере их чтения, вы можете сделать transform параметром *Функция 1020 *.Это делает его универсальным и позволяет читать содержимое пакета указанным пользователем способом -

const readPackages = async (transform, path = ".") =>
  Promise
    .all
      ( (await search ("package.json", path))
          .map (package => readFile (package))
      )
    .then
      ( buffers =>
          buffers .map (b => transform (JSON .parse (String (b))))
      )

readPackages
  ( ({ name }) => ({ name }) 
  , '.'
  )
  .then (console.log, console.error)

// [ { name: 'anotherpackage' }
// , { name: 'somepackage' }
// , { name: 'mypackage' }
// ]

или получать name, version и license -

readPackages
  ( ({ name, version, license = "None" }) =>
      ({ name, version, license }) 
  , '.'
  )
  .then (console.log, console.error)

// [ { name: 'anotherpackage', version: '1.0.0', license: 'None' }
// , { name: 'somepackage', version: '3.2.1', license: 'MIT' }
// , { name: 'mypackage', version: '1.2.3', license: 'BSD-3-Clause' }
// ]

Теперь в этих упрощенных программах мы начинаем видеть некоторые закономерности.Чтобы сделать наши намерения более понятными и избежать повторения, мы проектируем модуль многократного использования -

const Parallel = p =>
  ( { map: async f =>
        Promise .all ((await p) .map (x => f (x)))
    , filter: async f =>
        (await p) .filter (x => f (x))
    , flatMap: async f =>
        Promise .all ((await p) .map (x => f (x))) .then (ys => [] .concat (...ys))
    , // ...
    }
  )

Теперь наша функция files намного лучше -

const files = async (path = ".") =>
  (await stat (path)) .isDirectory ()
    ? Parallel (readdir (path))
        .flatMap (f => files (join (path, f)))
    : [ path ]

Наша searchфункция тоже немного очищена -

const search = async (query, path = ".") =>
  Parallel (files (path))
    .filter (x => basename (x) === query)

Наконец, readPackages -

const readPackages = async (f, path = ".") =>
  Parallel (search ("package.json", path))
    .map (readFile)
    .then
      ( buffers =>
          buffers .map (b => f (JSON .parse (String (b))))
      )

Поведение каждой функции идентично исходным реализациям.Только теперь у нас есть еще более общие функции, которые можно повторно использовать в других областях нашей программы.

В этих связанных вопросах и ответах мы используем модуль Parallel для реализации функции dirsкоторый рекурсивно перечисляет все каталоги по заданному пути.

0 голосов
/ 18 марта 2019

У вас есть несколько вариантов:

1) Поскольку все остальное выполняется синхронными методами fs, вы можете изменить fs.readdir на fs.readdirSync:

function searchForPackage(directory) {
  fs.readdirSync(directory).forEach(file => {
    var currentLocation = directory + "/" + file;
    if (fs.statSync(currentLocation).isDirectory() && file != 'bin' && file != '.bin') {
      searchForPackage(currentLocation);
      return;
    } else if (file == "package.json") {
      var content = fs.readFileSync(currentLocation);
      var jsonContent = JSON.parse(content);
      var obj = {
        name: jsonContent.name,
        license: jsonContent.license,
        version: jsonContent.version
      }
      jsonTable.push(obj);
      jsonTable.push({name: jsonContent.name, license: jsonContent.license, version: jsonContent.version});
      return;
    }
  })
}

2) Преобразовать fs.readdirSync в Promise, а затем использовать async/await:

async function searchForPackage(directory) {
  const files = await new Promise((resolve, reject) => {
    fs.readdir(directory, (err, files) => {
      if (err) reject(err);
      else resolve(files);
    });
  });
  await Promise.all(files.map(async file => {
    var currentLocation = directory + "/" + file;
    if (fs.statSync(currentLocation).isDirectory() && file != 'bin' && file != '.bin') {
      await searchForPackage(currentLocation);
      return;
    } else if (file == "package.json") {
      var content = fs.readFileSync(currentLocation);
      var jsonContent = JSON.parse(content);
      var obj = {
        name: jsonContent.name,
        license: jsonContent.license,
        version: jsonContent.version
      }
      jsonTable.push(obj);
      jsonTable.push({name: jsonContent.name, license: jsonContent.license, version: jsonContent.version});
      return;
    }
  }))
}

3) Используйте пару сторонних модулей, чтобы немного навести порядок (fs-extra позаботится о том, чтобы предложить вам асинхронные методы, такие как fs.readdir. async-af предоставляет цепочечные асинхронные методы JavaScript, такие как параллель forEach.):

const fs = require('fs-extra');
const AsyncAF = require('async-af');

async function searchForPackage(directory) {
  await AsyncAF(fs.readdir(directory)).forEach(async file => {
    var currentLocation = directory + "/" + file;
    if (fs.statSync(currentLocation).isDirectory() && file != 'bin' && file != '.bin') {
      await searchForPackage(currentLocation);
    } else if (file == "package.json") {
      var content = fs.readFileSync(currentLocation);
      var jsonContent = JSON.parse(content);
      var obj = {
        name: jsonContent.name,
        license: jsonContent.license,
        version: jsonContent.version
      }
      jsonTable.push(obj);
      jsonTable.push({name: jsonContent.name, license: jsonContent.license, version: jsonContent.version});
    }
  });
}
...