读取目录中的所有文件,将它们存储在对象中,并发送对象
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);
最后的回调也是错误的。它必须在目录中的所有文件都完成后调用。
但我不知道如何创建代码,有人知道这是否可能吗?
【问题讨论】:
如果同步访问没问题,您可以使用(阻塞)readfileSync
和readdirSync
方法跳过事件处理程序。 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
是它在数组中的位置,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))
那么所有的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;
);
【讨论】:
以上是关于读取目录中的所有文件,将它们存储在对象中,并发送对象的主要内容,如果未能解决你的问题,请参考以下文章