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

问题描述 投票:0回答:13

我不知道这是否可能,但就这样吧。使用回调会让事情变得更加困难。

我有一个包含 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
13个回答
225
投票

因此,共有三个部分。读取、存储和发送。

这是阅读部分:

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。异步回调不是一个好的风格。

此外,您还询问了有关剥离扩展的问题。你应该一一提出问题。没有人会专门为您编写完整的解决方案。


32
投票

对于下面的所有示例,您需要导入 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 的更多信息,请参见这篇文章

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

21
投票

这是前一个版本的现代

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
是其在数组中的位置,
resolve
reject
Promise
回调函数。 每个 Promise 将被推送到当前
index
处的数组中,并通过匿名函数调用将当前
item
作为参数:

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

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

5
投票

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

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
投票

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

4
投票

如果您有 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);
  });

1
投票

为了让代码在不同环境下都能顺利运行,可以在需要操作路径的地方使用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;
});

1
投票

异步/等待

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

谁想尝试使用发电机?


0
投票

所以,如果有人正在寻找一种适合初学者的方法,这是我的。

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

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

现在,让我们了解 Promise。 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++;
    });
  }

0
投票

在此示例中,我创建了一个数组。但如果你愿意,你可以创建一个对象

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

0
投票

现在已经是 2022 年了,现在有更清晰的本机函数。

import fs from "fs/promises";

const readDirectory = async (dir) => {
  const files = await fs.readdir(dir);

  const values = files.flatMap(async (file) => {
    const filePath = path.join(dir, file);
    const stat = await fs.stat(filePath);
    if (stat.isDirectory()) {
      return;
    }
    return fs.readFile(filePath);
  });

  const buffers = await Promise.all(values);
  // Remove this line to keep the raw buffers
  const contents = buffers.filter(Boolean).map((l) => l.toString());
  return contents;
};

还有打字稿版本

import fs from "fs/promises";

const readDirectory = async (dir: string) => {
  const files = await fs.readdir(dir);

  const values = files.flatMap(async (file) => {
    const filePath = path.join(dir, file);
    const stat = await fs.stat(filePath);
    if (stat.isDirectory()) {
      return;
    }
    return fs.readFile(filePath);
  }) as Array<Promise<Buffer>>;

  const buffers = await Promise.all(values);
  // Remove this line to keep the raw buffers
  const contents = buffers.filter(Boolean).map((l) => l.toString());
  return contents;
};

和递归版本

import fs from "fs/promises";

const getRecursiveFileReads = async (dir: string): Promise<Array<Promise<Buffer>>> => {
  const files = await fs.readdir(dir);
  return files.flatMap(async (file) => {
    const filePath = path.join(dir, file);
    const stat = await fs.stat(filePath);
    if (stat.isDirectory()) {
      return getRecursiveFileReads(filePath);
    }
    return fs.readFile(filePath);
  }) as Array<Promise<Buffer>>;
};
const readDirectory = async (dir: string) => {
  const promises = await getRecursiveFileReads(dir);
  const buffers = await Promise.all(promises);
  return buffers;
};

0
投票
const fs = require('node:fs');

async function lm_read_dir_files(myDir, chunks) {
  
  let count = 0;
  return new Promise(function(resolve, reject) {
    try {
      fs.readdir(myDir, (error, fileNames) => {
        count = fileNames.length; 
        
        fileNames.forEach(filename => {
          fs.readFile(myDir + filename, 'utf8', (err, data) => {
            count--;
            chunks.push({filename, data});
            if(count == 0) {
              resolve('pass');
            }
          });
        });        
        
      });
    }catch(err) {
      reject('fail');
    }
  });
}

//Usage as below
let chunks = [];
let myDir = '/home/user/errors/m-error/';
await lm_read_dir_files(myDir, chunks);
console.log(chunks);
© www.soinside.com 2019 - 2024. All rights reserved.