NodeJS:关于异步"readdir"的困惑;和"stat" [英] NodeJS: Confusion about async "readdir" and "stat"

查看:165
本文介绍了NodeJS:关于异步"readdir"的困惑;和"stat"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在文档中,它显示了 readdir 的两个版本a>和状态.两者都有异步和同步版本readir/readdirSyncstat/statSync.

In the docs it shows two versions of readdir and stat. Both of which have an async and sync version readir/readdirSync and stat/statSync.

因为readidirstat是异步的,所以我希望它们返回Promise,但是当尝试使用async/await时,脚本不会等待readdir解析,如果我使用.then/.catch,则会收到错误消息cannot read .then of undefined.

Because readidir and stat are async I would expect them to return a Promise but when trying to use async/await the script doesnt wait for readdir to resolve and if I use .then/.catch I get an error cannot read .then of undefined.

我在这里要做的就是将脚本在其中运行的目录中存在的目录映射到dirsOfCurrentDir映射.

All I'm trying to do here is map the directories that exist inside of the directory the script is being ran inside of to the dirsOfCurrentDir map.

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)

编辑

我最终使用了这两个功能readdirSyncstatSync的同步版本.尽管使用异步方法或承诺使用会感觉更好,但我仍然没有弄清楚如何使用这两种方法来使我的代码正常工作.

Edit

I ended up going with the synchronous versions of both of these functions readdirSync and statSync. While I would feel better using the async methods or promisify I still have not figured out how to get my code working correctly using either.

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

推荐答案

由于readidir和stat是异步的,我希望它们返回Promise

Because readidir and stat are async I would expect them to return a Promise

首先,请确保您知道异步函数和async函数之间的区别.使用Java中的特定关键字声明为async的函数,例如:

First off, make sure you know the difference between an asynchronous function and an async function. A function declared as async using that specific keyword in Javascript such as:

async function foo() {
    ...
}

总是返回一个Promise(根据用async关键字声明的函数的定义).

does always return a promise (per the definition of a function declared with the async keyword).

但是,诸如fs.readdir()之类的异步函数可能会或可能不会返回promise,具体取决于其内部设计.在这种特殊情况下,node.js中fs模块的原始实现仅使用回调,而不使用诺言(其设计早于诺言.js中存在诺言).它的功能是异步的,但未声明为async,因此使用常规回调,而不是promises.

But an asynchronous function such as fs.readdir() may or may not return a promise, depending upon its internal design. In this particular case, the original implementation of the fs module in node.js only uses callbacks, not promises (its design predates the existence of promises in node.js). Its functions are asynchronous, but not declared as async and thus is uses regular callbacks, not promises.

因此,您必须使用回调或"promisify"接口将其转换为可返回promise的内容,以便您可以将其与await一起使用.

So, you have to either use the callbacks or "promisify" the interface to convert it into something that returns a promise so you can use await with it.

node.js中有一个实验界面v10 为fs模块提供了内置的Promise.

There is an experimental interface in node.js v10 that offers built-in promises for the fs module.

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 Promise库并一次实现整个fs库的承诺:


Since I'm not yet developing on node v10, I often use the Bluebird promise library and promisify the whole fs library at once:

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

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


要仅列出给定目录中的子目录,可以执行以下操作:


To just list the sub-directories in a given directory, you could do this:

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);
    }
});


这是一个更通用的实现,它列出文件并为列出内容和如何显示结果提供几个选项:


Here's a more general implementation that lists files and offers several options for both what to list and how to present the results:

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);
});

您可以将此代码复制到文件中并运行它,并将.作为参数传递,以列出脚本目录或要列出的任何子目录名称.

You can copy this code into a file and run it, passing . as an argument to list the directory of the script or any subdirectory name you want to list.

如果您想要更少的选项(例如不递归或不保留目录顺序),则可以大大减少此代码,甚至可以使其速度更快(并行运行一些异步操作).

If you wanted fewer options (such as no recursion or directory order not preserved), this code could be reduced significantly and perhaps made a little faster (run some async operations in parallel).

这篇关于NodeJS:关于异步"readdir"的困惑;和"stat"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆