NodeJS: путаница с асинхронными «readdir» и «stat» - PullRequest
0 голосов
/ 04 июля 2018

В документах показаны две версии readdir и stat . Оба варианта имеют асинхронную и синхронизированную версии readir/readdirSync и stat/statSync.

Поскольку readidir и stat являются асинхронными, я ожидаю, что они вернут Обещание, но при попытке использовать async/await сценарий не ожидает разрешения readdir, и если я использую .then/.catch, я получаю ошибку cannot read .then of undefined.

Все, что я пытаюсь сделать, это сопоставить каталоги, которые существуют внутри каталога, внутри которого запускается скрипт, с картой dirsOfCurrentDir.

Возвращает ошибку cannot read .then of undefined

const fs = require('fs');

const directory = `${ __dirname }/${ process.argv[2] }`;
const dirsOfCurrentDir = new Map();

fs.readdir(directory, (err, files) => {
  let path;

  if (err)
    return console.log(err);

  files.forEach(file => {
    path = directory + file;

    fs.stat(path, (err, stats) => {
      if (err)
        return console.log(err);

      dirsOfCurrentDir.set(file, directory);
    });
  });
}).then(() => console.log('adasdasd'))

console.log(dirsOfCurrentDir)

Возвращает Map {}

const foo = async () => {
  await fs.readdir(directory, (err, files) => {
    let path;

    if (err)
      return console.log(err);

    files.forEach(file => {
      path = directory + file;

      fs.stat(path, (err, stats) => {
        if (err)
          return console.log(err);

        dirsOfCurrentDir.set(file, directory);
      });
    });
  });
};

foo()
console.log(dirsOfCurrentDir)

Редактировать

Я закончил с синхронными версиями обеих этих функций readdirSync и statSync. Хотя я чувствовал бы себя лучше, используя асинхронные методы или обещание, я все еще не понял, как заставить мой код работать правильно, используя либо.

const fs = require('fs');

const directory = `${ __dirname }/${ process.argv[2] }`;
const dirsOfCurrentDir = new Map();

const dirContents = fs.readdirSync(directory);

dirContents.forEach(file => {
  const path = directory + file;
  const stats = fs.statSync(path);

  if (stats.isDirectory())
    dirsOfCurrentDir.set(file, path);
});

console.log(dirsOfCurrentDir); // logs out the map with all properties set

1 Ответ

0 голосов
/ 04 июля 2018

Поскольку readidir и stat асинхронны, я ожидаю, что они вернут Обещание

Прежде всего, убедитесь, что вы знаете разницу между асинхронной функцией и функцией async. Функция, объявленная как async с использованием этого конкретного ключевого слова в Javascript, такого как:

async function foo() {
    ...
}

всегда возвращает обещание (согласно определению функции, объявленной с ключевым словом async).

Но асинхронная функция, такая как fs.readdir(), может возвращать или не возвращать обещание, в зависимости от его внутреннего дизайна. В этом конкретном случае исходная реализация модуля fs в node.js использует только обратные вызовы, а не обещания (его конструкция предшествует существованию обещаний в node.js). Его функции асинхронны, но не объявлены как async и поэтому используют обычные обратные вызовы, а не обещания.

Итак, вы должны либо использовать обратные вызовы, либо «обещать» интерфейс, чтобы преобразовать его во что-то, что возвращает обещание, чтобы вы могли использовать с ним await.

В файле node.js v10 имеется экспериментальный интерфейс , который предлагает встроенные обещания для модуля fs.

const fsp = require('fs').promises;

fsp.readdir(...).then(...)

В более ранней версии node.js. есть много вариантов для обещания функций. Вы можете сделать это с помощью функции util.promisify () :

const promisify = require('util').promisify;
const readdirP = promisify(fs.readdir);
const statP = promisify(fs.stat);

Поскольку я еще не занимаюсь разработкой на узле v10, я часто использую библиотеку обещаний Bluebird и обещаю сразу всю библиотеку fs:

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

fs.readdirAsync(...).then(...)

Чтобы просто перечислить подкаталоги в данном каталоге, вы можете сделать это:

const fs = require('fs');
const path = require('path');
const promisify = require('util').promisify;
const readdirP = promisify(fs.readdir);
const statP = promisify(fs.stat);

const root = path.join(__dirname, process.argv[2]);

// utility function for sequencing through an array asynchronously
function sequence(arr, fn) {
    return arr.reduce((p, item) => {
        return p.then(() => {
            return fn(item);
        });
    }, Promise.resolve());
}

function listDirs(rootDir) {
    const dirsOfCurrentDir = new Map();
    return readdirP(rootDir).then(files => {
        return sequence(files, f => {
            let fullPath = path.join(rootDir, f);
            return statP(fullPath).then(stats => {
                if (stats.isDirectory()) {
                    dirsOfCurrentDir.set(f, rootDir)
                }
            });
        });
    }).then(() => {
        return dirsOfCurrentDir;
    });  
}

listDirs(root).then(m => {
    for (let [f, dir] of m) {
        console.log(f);
    }
});

Вот более общая реализация, которая перечисляет файлы и предлагает несколько опций для того, что перечислять и как представлять результаты:

const fs = require('fs');
const path = require('path');
const promisify = require('util').promisify;
const readdirP = promisify(fs.readdir);
const statP = promisify(fs.stat);

const root = path.join(__dirname, process.argv[2]);

// options takes the following:
//     recurse: true | false - set to true if you want to recurse into directories (default false)
//     includeDirs: true | false - set to true if you want directory names in the array of results
//     sort: true | false - set to true if you want filenames sorted in alpha order
//     results: can have any one of the following values
//              "arrayOfFilePaths" - return an array of full file path strings for files only (no directories included in results)
//              "arrayOfObjects" - return an array of objects {filename: "foo.html", rootdir: "//root/whatever", full: "//root/whatever/foo.html"}

// results are breadth first

// utility function for sequencing through an array asynchronously
function sequence(arr, fn) {
    return arr.reduce((p, item) => {
        return p.then(() => {
            return fn(item);
        });
    }, Promise.resolve());
}

function listFiles(rootDir, opts = {}, results = []) {
    let options = Object.assign({recurse: false, results: "arrayOfFilePaths", includeDirs: false, sort: false}, opts);

    function runFiles(rootDir, options, results) {
        return readdirP(rootDir).then(files => {
            let localDirs = [];
            if (options.sort) {
                files.sort();
            }
            return sequence(files, fname => {
                let fullPath = path.join(rootDir, fname);
                return statP(fullPath).then(stats => {
                    // if directory, save it until after the files so the resulting array is breadth first
                    if (stats.isDirectory()) {
                        localDirs.push({name: fname, root: rootDir, full: fullPath, isDir: true});
                    } else {
                        results.push({name: fname, root: rootDir, full: fullPath, isDir: false});
                    }
                });
            }).then(() => {
                // now process directories
                if (options.recurse) {
                    return sequence(localDirs, obj => {
                        // add directory to results in place right before its files
                        if (options.includeDirs) {
                            results.push(obj);
                        }
                        return runFiles(obj.full, options, results);
                    });
                } else {
                    // add directories to the results (after all files)
                    if (options.includeDirs) {
                        results.push(...localDirs);
                    }
                }
            });
        });
    }

    return runFiles(rootDir, options, results).then(() => {
        // post process results based on options
        if (options.results === "arrayOfFilePaths") {
            return results.map(item => item.full);
        } else {
            return results;
        }
    });
}

// get flat array of file paths, 
//     recursing into directories, 
//     each directory sorted separately
listFiles(root, {recurse: true, results: "arrayOfFilePaths", sort: true, includeDirs: false}).then(list => {
    for (const f of list) {
        console.log(f);
    }
}).catch(err => {
    console.log(err);
});

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

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

...