【问题标题】: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});

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

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

【问题讨论】:

标签: node.js


【解决方案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】:

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

            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://stackoverflow.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://stackoverflow.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;
                });
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-08-21
                  • 1970-01-01
                  • 2021-01-07
                  • 2021-03-06
                  • 2023-03-10
                  • 2012-02-22
                  相关资源
                  最近更新 更多