读取目录中的所有文件,将它们存储在对象中,并发送对象

Posted

技术标签:

【中文标题】读取目录中的所有文件,将它们存储在对象中,并发送对象【英文标题】:Reading all files in a directory, store them in objects, and send the object 【发布时间】:2012-04-20 10:19:09 【问题描述】:

我不知道这是否可能,但是就这样吧。而使用回调则更加困难。

我有一个包含 html 文件的目录,我想使用 node.js 和 socket.io 以对象块的形式将这些文件发送回客户端。

我所有的文件都在 /tmpl 中

所以socket需要读取/tmpl中的所有文件。

对于每个文件,它必须将数据存储在一个对象中,文件名作为键,内容作为值。

  var data;
  // this is wrong because it has to loop trough all files.
  fs.readFile(__dirname + '/tmpl/filename.html', 'utf8', function(err, html)
      if(err) throw err;
      //filename must be without .html at the end
      data['filename'] = html;
  );
  socket.emit('init', data: data);

最后的回调也是错误的。它必须在目录中的所有文件都完成后调用。

但我不知道如何创建代码,有人知道这是否可能吗?

【问题讨论】:

如果同步访问没问题,您可以使用(阻塞)readfileSyncreaddirSync 方法跳过事件处理程序。 nodejs.org/docs/v0.4.8/api/fs.html#fs.readdirSync 好的,我不知道 readdir,这可能会有所帮助。阻塞的缺点是什么。我认为 node.js 的全部意义在于它是非阻塞的?为什么我们能一下子挡住。 对于异步回调请阅读:***.com/questions/18983138/… 有很多错误的答案,但有些是正确的。其中一个使用计数器。 或者这个:***.com/questions/15162049/… 或者这个***.com/questions/10390041/… 【参考方案1】:

在这个例子中,我创建了一个数组。但是你可以根据需要创建一个对象

const fs = require('fs-extra')

const dirname = `$process.cwd()/test`
const fileNames = await fs.readdir(dirname)

const files = []
for(const fileName of fileNames)
    const file = await fs.readFile(`$dirname/$fileName`, 'utf8')
    files.push(file)
          

【讨论】:

【参考方案2】:

所以,如果有人正在寻找对初学者友好的方法,这是我的。

在实际解决之前,我们必须了解异步函数promises。 异步函数在单线程范围之外运行,可以与主线程并行运行。这基本上意味着,如果一个函数是异步的,javascript 不会等待函数完成,而是会转到下一行。该函数将并行执行。

fs.readFile() 是异步的,因此它执行下一行,并并行运行,

现在,让我们了解一下承诺。 Promise 基本上是一个对象,它返回异步函数的成功或失败。 例如:

//Defining Promise
const promise = new Promise((resolve,reject)=>
    //This is an asynchronous function, which takes 2 seconds to execute
    setTimeout(()=>
        if(1!=0)
            //If there is an error reject the promise
            reject(new Error("This is an error messahe"))
         
        else
            //if there are no errors we resolve the promise
            resolve('userId':'id')
        
    ,2000)
)

现在,原来的问题来了

const fs = require("fs");

// Read the File Names.
function readFileNames() 
  // Defining a new promise
  return new Promise((resolve, reject) => 
    try 
      //read the directory
      fs.readdir("./public/", (err, files) => 
        // If read completes, resolve the promise.
        resolve(files);
      );
     catch (err) 
      // If there is an error, reject the promise. 
      reject(err);
    
  );


// Read content of a given file
function readFileContent(file) 
  return new Promise((resolve, reject) => 
    try 
      fs.readFile("./public/" + file, "utf8", (err, content) => 
        resolve(content);
      );
     catch (err) 
      reject(err);
    
  );


//sending the data
module.exports = 
// If we want to wait for a function to wait for a promise to be 
// resolved we define it as 'async'
  async get(req, res) 
    let data = ;
    //Here we wait for the promise to resolve, thus await is used
    const fileNames = await readFileNames();
    let count = 0;
    // Note we again use async since we want to wait for promise
    fileNames.forEach(async (name) => 
      // We wait for the content of file.
      const content = await readFileContent(name);
      data[name] = content;
      // Since we want to send data after the loop is completed.
      if (count === fileNames.length - 1) 
        res.send(data);
      
      count++;
    );
  

【讨论】:

【参考方案3】:

我刚刚写了这个,它看起来更干净:

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

const readdir = util.promisify(fs.readdir);
const readFile = util.promisify(fs.readFile);

const readFiles = async dirname => 
    try 
        const filenames = await readdir(dirname);
        console.log( filenames );
        const files_promise = filenames.map(filename => 
            return readFile(dirname + filename, 'utf-8');
        );
        const response = await Promise.all(files_promise);
        //console.log( response )
        //return response
        return filenames.reduce((accumlater, filename, currentIndex) => 
            const content = response[currentIndex];
            accumlater[filename] = 
                content,
            ;
            return accumlater;
        , );
     catch (error) 
        console.error(error);
    
;

const main = async () => 

    const response = await readFiles(
        './folder-name',
    );
    console.log( response );
;

您可以根据需要修改response 格式。 此代码中的response 格式如下所示:


   "filename-01":
      "content":"This is the sample content of the file"
   ,
   "filename-02":
      "content":"This is the sample content of the file"
   


【讨论】:

【参考方案4】:

异步/等待

const  promisify  = require("util")
const directory = path.join(__dirname, "/tmpl")
const pathnames = promisify(fs.readdir)(directory)

try 
  async function emitData(directory) 
    let filenames = await pathnames
    var ob = 
    const data = filenames.map(async function(filename, i) 
      if (filename.includes(".")) 
        var storedFile = promisify(fs.readFile)(directory + `\\$filename`, 
          encoding: "utf8",
        )
        ob[filename.replace(".js", "")] = await storedFile
        socket.emit("init",  data: ob )
      
      return ob
    )
  

  emitData(directory)
 catch (err) 
  console.log(err)

谁想尝试使用生成器?

【讨论】:

【参考方案5】:

你是像我这样的懒人,喜欢 npm 模块 :D 然后看看这个。

npm install node-dir

读取文件示例:

var dir = require('node-dir');

dir.readFiles(__dirname,
    function(err, content, next) 
        if (err) throw err;
        console.log('content:', content);  // get content of files
        next();
    ,
    function(err, files)
        if (err) throw err;
        console.log('finished reading files:', files); // get filepath 
   );    

【讨论】:

【参考方案6】:

对于以下所有示例,您需要导入 fs 和 path 模块:

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

异步读取文件

function readFiles(dir, processFile) 
  // read directory
  fs.readdir(dir, (error, fileNames) => 
    if (error) throw error;

    fileNames.forEach(filename => 
      // get current file name
      const name = path.parse(filename).name;
      // get current file extension
      const ext = path.parse(filename).ext;
      // get current file path
      const filepath = path.resolve(dir, filename);

      // get information about the file
      fs.stat(filepath, function(error, stat) 
        if (error) throw error;

        // check if the current path is a file or a folder
        const isFile = stat.isFile();

        // exclude folders
        if (isFile) 
          // callback, do something with the file
          processFile(filepath, name, ext, stat);
        
      );
    );
  );

用法:

// use an absolute path to the folder where files are located
readFiles('absolute/path/to/directory/', (filepath, name, ext, stat) => 
  console.log('file path:', filepath);
  console.log('file name:', name);
  console.log('file extension:', ext);
  console.log('file information:', stat);
);

同步读取文件,存储在数组中,自然排序

/**
 * @description Read files synchronously from a folder, with natural sorting
 * @param String dir Absolute path to directory
 * @returns Object[] List of object, each object represent a file
 * structured like so: ` filepath, name, ext, stat `
 */
function readFilesSync(dir) 
  const files = [];

  fs.readdirSync(dir).forEach(filename => 
    const name = path.parse(filename).name;
    const ext = path.parse(filename).ext;
    const filepath = path.resolve(dir, filename);
    const stat = fs.statSync(filepath);
    const isFile = stat.isFile();

    if (isFile) files.push( filepath, name, ext, stat );
  );

  files.sort((a, b) => 
    // natural sort alphanumeric strings
    // https://***.com/a/38641281
    return a.name.localeCompare(b.name, undefined,  numeric: true, sensitivity: 'base' );
  );

  return files;

用法:

// return an array list of objects
// each object represent a file
const files = readFilesSync('absolute/path/to/directory/');

使用 promise 异步读取文件

更多关于promisify 的信息请参见article。

const  promisify  = require('util');

const readdir_promise = promisify(fs.readdir);
const stat_promise = promisify(fs.stat);

function readFilesAsync(dir) 
  return readdir_promise(dir,  encoding: 'utf8' )
    .then(filenames => 
      const files = getFiles(dir, filenames);

      return Promise.all(files);
    )
    .catch(err => console.error(err));


function getFiles(dir, filenames) 
  return filenames.map(filename => 
    const name = path.parse(filename).name;
    const ext = path.parse(filename).ext;
    const filepath = path.resolve(dir, filename);

    return stat( name, ext, filepath );
  );


function stat( name, ext, filepath ) 
  return stat_promise(filepath)
    .then(stat => 
      const isFile = stat.isFile();

      if (isFile) return  name, ext, filepath, stat ;
    )
    .catch(err => console.error(err));

用法:

readFiles('absolute/path/to/directory/')
  // return an array list of objects
  // each object is a file
  // with those properties:  name, ext, filepath, stat 
  .then(files => console.log(files))
  .catch(err => console.log(err));

注意:为文件夹返回undefined,如果您愿意,可以将它们过滤掉:

readFiles('absolute/path/to/directory/')
  .then(files => files.filter(file => file !== undefined))
  .catch(err => console.log(err));

【讨论】:

【参考方案7】:

如果你有 Node.js 8 或更高版本,你可以使用新的 util.promisify。 (我将与重新格式化为对象有关的代码部分标记为可选,这是原始帖子要求的。)

  const fs = require('fs');
  const  promisify  = require('util');

  let files; // optional
  promisify(fs.readdir)(directory).then((filenames) => 
    files = filenames; // optional
    return Promise.all(filenames.map((filename) => 
      return promisify(fs.readFile)(directory + filename, encoding: 'utf8');
    ));
  ).then((strArr) => 
    // optional:
    const data = ;
    strArr.forEach((str, i) => 
      data[files[i]] = str;
    );
    // send data here
  ).catch((err) => 
    console.log(err);
  );

【讨论】:

【参考方案8】:

采用 Promise 的现代方法的另一个版本。其他基于 Promise 的响应更短:

const readFiles = (dirname) => 

  const readDirPr = new Promise( (resolve, reject) => 
    fs.readdir(dirname, 
      (err, filenames) => (err) ? reject(err) : resolve(filenames))
  );

  return readDirPr.then( filenames => Promise.all(filenames.map((filename) => 
      return new Promise ( (resolve, reject) => 
        fs.readFile(dirname + filename, 'utf-8',
          (err, content) => (err) ? reject(err) : resolve(content));
      )
    )).catch( error => Promise.reject(error)))
;

readFiles(sourceFolder)
  .then( allContents => 

    // handle success treatment

  , error => console.log(error));

【讨论】:

又好又脆!谢谢@Paul【参考方案9】:

这是前一个的现代Promise 版本,使用Promise.all 方法在读取所有文件后解决所有承诺:

/**
 * Promise all
 * @author Loreto Parisi (loretoparisi at gmail dot com)
 */
function promiseAllP(items, block) 
    var promises = [];
    items.forEach(function(item,index) 
        promises.push( function(item,i) 
            return new Promise(function(resolve, reject) 
                return block.apply(this,[item,index,resolve,reject]);
            );
        (item,index))
    );
    return Promise.all(promises);
 //promiseAll

/**
 * read files
 * @param dirname string
 * @return Promise
 * @author Loreto Parisi (loretoparisi at gmail dot com)
 * @see http://***.com/questions/10049557/reading-all-files-in-a-directory-store-them-in-objects-and-send-the-object
 */
function readFiles(dirname) 
    return new Promise((resolve, reject) => 
        fs.readdir(dirname, function(err, filenames) 
            if (err) return reject(err);
            promiseAllP(filenames,
            (filename,index,resolve,reject) =>  
                fs.readFile(path.resolve(dirname, filename), 'utf-8', function(err, content) 
                    if (err) return reject(err);
                    return resolve(filename: filename, contents: content);
                );
            )
            .then(results => 
                return resolve(results);
            )
            .catch(error => 
                return reject(error);
            );
        );
  );

如何使用它:

就这么简单:

readFiles( EMAIL_ROOT + '/' + folder)
.then(files => 
    console.log( "loaded ", files.length );
    files.forEach( (item, index) => 
        console.log( "item",index, "size ", item.contents.length);
    );
)
.catch( error => 
    console.log( error );
);

假设您有另一个文件夹列表,您也可以遍历此列表,因为内部 promise.all 将异步解析每个文件夹:

var folders=['spam','ham'];
folders.forEach( folder => 
    readFiles( EMAIL_ROOT + '/' + folder)
    .then(files => 
        console.log( "loaded ", files.length );
        files.forEach( (item, index) => 
            console.log( "item",index, "size ", item.contents.length);
        );
    )
    .catch( error => 
        console.log( error );
    );
);

工作原理

promiseAll 具有魔力。它采用签名function(item,index,resolve,reject) 的函数块,其中item 是数组中的当前项,index 是它在数组中的位置,resolverejectPromise 回调函数。 每个 promise 都将被推送到当前 index 的数组中,并通过匿名函数调用将当前 item 作为参数:

promises.push( function(item,i) 
        return new Promise(function(resolve, reject) 
            return block.apply(this,[item,index,resolve,reject]);
        );
    (item,index))

那么所有的promise都会被解决:

return Promise.all(promises);

【讨论】:

很棒的代码 Loreto,但为什么不使用 return block(item,index,resolve,reject); 而不是 return block.apply(this,[item,index,resolve,reject]);,我认为 apply 更难理解 - 有没有我不知道的好处? @NULLpointer 谢谢。 apply 的好处之一是您可以使用数组来传递 args,而且您还可以传递定义变量的上下文,例如 self.apply(someObjContext, [arg1,arg2])。在这种特定情况下,您实际上并不需要它,但是如果您在库中,则此对象上下文可能是其他东西...【参考方案10】:

所以,分为三个部分。读取、存储和发送。

阅读部分如下:

var fs = require('fs');

function readFiles(dirname, onFileContent, onError) 
  fs.readdir(dirname, function(err, filenames) 
    if (err) 
      onError(err);
      return;
    
    filenames.forEach(function(filename) 
      fs.readFile(dirname + filename, 'utf-8', function(err, content) 
        if (err) 
          onError(err);
          return;
        
        onFileContent(filename, content);
      );
    );
  );

这是存储部分:

var data = ;
readFiles('dirname/', function(filename, content) 
  data[filename] = content;
, function(err) 
  throw err;
);

发送部分由您决定。您可能希望一一发送或在阅读完成后发送。

如果您想在阅读完成后发送文件,您应该使用 fs 函数的同步版本或使用 Promise。异步回调不是一个好的风格。

此外,您还询问了关于剥离扩展名的问题。你应该一一回答问题。没有人会只为您编写完整的解决方案。

【讨论】:

谢谢,我想我会用这个。有一件事,你能解释一下0===--c 做了什么吗? 你可以把它写成两行c-- 然后if (c===0) 那是一样的。它只是将c 递减1 并检查它是否达到零 由于异步性质,foreach 循环很可能在返回第一个 readFile html 之前完成,因此 c 应该立即上升到 x(文件数),然后在html 从磁盘到达(要晚得多) 哦,那是一些深刻的逻辑,很好。有很多关于节点的知识。感谢您的帮助! 感谢您阅读存储部分。这对我真的很有帮助。有人可以举一个例子,说明在这种情况下如何使用 Promises 在完成后发送所有文件?【参考方案11】:

为了让代码在不同的环境中顺利运行,path.resolve 可以用于路径被操纵的地方。这是效果更好的代码。

阅读部分:

var fs = require('fs');

function readFiles(dirname, onFileContent, onError) 
  fs.readdir(dirname, function(err, filenames) 
    if (err) 
      onError(err);
      return;
    
    filenames.forEach(function(filename) 
      fs.readFile(path.resolve(dirname, filename), 'utf-8', function(err, content) 
        if (err) 
          onError(err);
          return;
        
        onFileContent(filename, content);
      );
    );
  );

存储部分:

var data = ;
readFiles(path.resolve(__dirname, 'dirname/'), function(filename, content) 
  data[filename] = content;
, function(error) 
  throw err;
);

【讨论】:

以上是关于读取目录中的所有文件,将它们存储在对象中,并发送对象的主要内容,如果未能解决你的问题,请参考以下文章

读取所有 .md 文件,将它们转换为 html 并发送它们

将drawable中的图像作为文件读取

将地址存储到向量中的堆栈分配对象

读取“文件”对象的内容?

读取 QDataStream 中的特定对象并计算存储的对象数

在 C++ 中读取具有动态数组的对象