我不知道这是否可能,但就这样吧。使用回调会让事情变得更加困难。
我有一个包含 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});
最后的回调也是错误的。当目录中的所有文件都完成时必须调用它。
但是我不知道如何创建代码,有人知道这是否可能吗?
因此,共有三个部分。读取、存储和发送。
这是阅读部分:
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。异步回调不是一个好的风格。
此外,您还询问了有关剥离扩展的问题。你应该一一提出问题。没有人会专门为您编写完整的解决方案。
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/');
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));
这是前一个版本的现代
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);
你是像我一样的懒人并且喜欢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
});
我刚刚写了这个,对我来说看起来更干净:
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"
}
}
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));
如果您有 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);
});
为了让代码在不同环境下都能顺利运行,可以在需要操作路径的地方使用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;
});
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)
}
谁想尝试使用发电机?
所以,如果有人正在寻找一种适合初学者的方法,这是我的。
在实际解决方案之前,我们必须了解异步函数和承诺。 异步函数在单线程范围之外运行,并且可以与主线程并行运行。它基本上意味着,如果一个函数是异步的,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++;
});
}
在此示例中,我创建了一个数组。但如果你愿意,你可以创建一个对象
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)
}
现在已经是 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;
};
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);