112

我不知道这是否可能,但这里有。使用回调使其变得更加困难。

我有一个包含 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});

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

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

4

11 回答 11

198

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

下面是阅读部分:

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

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

于 2012-04-06T21:57:09.760 回答
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变魔术。它需要一个功能块 signature function(item,index,resolve,reject)item数组中的当前项在哪里,它在数组中的index位置resolve以及回调函数。每个 Promise 都将被推送到当前数组中,并通过匿名函数调用将当前作为参数:rejectPromiseindexitem

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);
于 2017-04-20T09:00:33.237 回答
18

对于下面的所有示例,您需要导入fs路径模块:

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));
于 2018-04-01T18:33:58.680 回答
5

你是像我这样的懒人并且喜欢npm 模块吗:D 然后检查一下。

npm 安装节点目录

读取文件的示例:

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 
   });    
于 2017-12-03T10:06:22.390 回答
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));
于 2017-09-06T08:22:33.287 回答
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);
  });
于 2018-01-01T07:32:10.747 回答
2

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

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

于 2020-09-11T12:33:00.347 回答
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;
});
于 2016-08-24T21:53:35.707 回答
0

异步/等待

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

谁想尝试使用发电机?

于 2020-04-29T06:39:52.417 回答
0

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

在实际解决之前,我们必须了解异步函数Promise。异步函数在单线程范围之外运行,可以与主线程并行运行。这基本上意味着,如果一个函数是异步的,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++;
    });
  }
于 2021-11-15T04:20:31.400 回答
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)
}          
于 2021-12-07T13:34:55.160 回答