Đọc tất cả các tệp trong một thư mục, lưu trữ chúng trong các đối tượng và gửi đối tượng


91

Tôi không biết nếu điều này là có thể, nhưng đây là. Và làm việc với các cuộc gọi lại khiến nó thậm chí còn khó khăn hơn.

Tôi có một thư mục chứa các tệp html mà tôi muốn gửi lại cho máy khách trong phần Object với node.js và socket.io.

Tất cả các tệp của tôi đều ở trong / tmpl

Vì vậy socket cần đọc tất cả các tệp trong / tmpl.

đối với mỗi tệp, nó phải lưu trữ dữ liệu trong một đối tượng với tên tệp là khóa và nội dung là giá trị.

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

Lần gọi lại cuối cùng cũng sai. Nó phải được gọi khi tất cả các tệp trong thư mục được thực hiện xong.

Nhưng tôi không biết làm thế nào để tạo mã, có ai biết nếu đây là Possibel?


4
Nếu truy cập đồng bộ là ổn, bạn có thể bỏ qua trình xử lý sự kiện bằng cách sử dụng (chặn) readfileSyncvà readdirSynccác phương thức. nodejs.org/docs/v0.4.8/api/fs.html#fs.readdirSync
— rjz

Ok, tôi không biết về readdir, điều đó có thể hữu ích. Và những mặt trái của việc chặn là gì. Tôi nghĩ rằng toàn bộ điểm của node.js là nó không bị chặn? Tại sao tất cả chúng ta có thể đột ngột tắc nghẽn.
— Saif Bechan

Đối với các lệnh gọi lại không đồng bộ, hãy đọc phần này: stackoverflow.com/questions/18983138/… Có nhiều câu trả lời sai, nhưng một số câu đúng. Một trong số họ sử dụng bộ đếm.
— Vanuan

Câu trả lời:


169

Vì vậy, có ba phần. Đọc, lưu trữ và gửi.

Đây là phần đọc:

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

Đây là phần lưu trữ:

var data = {};
readFiles('dirname/', function(filename, content) {
  data[filename] = content;
}, function(err) {
  throw err;
});

Phần gửi là tùy thuộc vào bạn. Bạn có thể muốn gửi từng cái một hoặc sau khi đọc xong.

Nếu bạn muốn gửi tệp sau khi đọc xong, bạn nên sử dụng các phiên bản đồng bộ của các fschức năng hoặc sử dụng các hứa hẹn. Gọi lại không đồng bộ không phải là một phong cách tốt.

Ngoài ra, bạn đã hỏi về việc loại bỏ tiện ích mở rộng. Bạn nên tiếp tục với từng câu hỏi một. Không ai sẽ viết một giải pháp hoàn chỉnh chỉ dành cho bạn.


Cảm ơn, tôi nghĩ tôi sẽ sử dụng cái này. Một điều tho, bạn có thể giải thích những gì 0===--ckhông.
— Saif Bechan

1
Bạn có thể viết nó thành hai dòng c--và sau đó if (c===0)giống nhau. Nó chỉ decrements cbằng 1và kiểm tra nếu nó đạt zero
— stewe

Nhưng nó sẽ luôn luôn là 0, hay không? Bạn thêm 1 vào foreach, và trong cùng foreach, bạn xóa 1, vì vậy nó luôn là 0, hay tôi nhầm? Không cần phải có séc if(c===files.length), đại loại như vậy.
— Saif Bechan

5
Do tính chất async vòng lặp foreach rất có thể kết thúc trước khi là người đầu tiên readFilehtml được trả về, vì vậy cnên đi đến x(số file) ngay lập tức và sau đó giảm giá trị khi html đến từ đĩa (mà là nhiều hơn)
— stewe

1
Ồ, đó là một số logic sâu sắc ở đó, một cái hay. Có rất nhiều điều để tìm hiểu về nút. Cảm ơn đã giúp đỡ!
— Saif Bechan

16

Đây là Promisephiên bản hiện đại của phiên bản trước, sử dụng cách Promise.alltiếp cận để giải quyết tất cả các lời hứa khi tất cả các tệp đã được đọc:

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

Làm thế nào để sử dụng nó:

Chỉ đơn giản như làm:

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

Giả sử rằng bạn có một danh sách các thư mục khác, bạn cũng có thể lặp lại danh sách này, vì nội bộ Prom.all sẽ giải quyết từng thư mục một cách không đồng bộ:

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

Làm thế nào nó hoạt động

Điều promiseAllkỳ diệu không. Nó có một khối chức năng của chữ ký function(item,index,resolve,reject), vị trí itemcủa mục hiện tại trong mảng, indexvị trí của nó trong mảng resolvevà rejectcác Promisehàm gọi lại. Mỗi lời hứa sẽ được đẩy vào một mảng ở thời điểm hiện tại indexvà với hiện tại itemlà các đối số thông qua một lệnh gọi hàm ẩn danh:

promises.push( function(item,i) {
        return new Promise(function(resolve, reject) {
            return block.apply(this,[item,index,resolve,reject]);
        });
    }(item,index))

Sau đó, tất cả các lời hứa sẽ được giải quyết:

return Promise.all(promises);

1
Mã Loreto tuyệt vời, nhưng tại sao không sử dụng return block(item,index,resolve,reject);thay vì return block.apply(this,[item,index,resolve,reject]);, tôi nghĩ rằng applynó làm cho nó khó hiểu hơn - có lợi ích mà tôi không biết không?
— trỏ NULL

1
@NULLpointer cảm ơn. Một trong những lợi ích của ứng dụng này là bạn có thể sử dụng mảng để truyền args và hơn thế nữa, bạn có thể chuyển ngữ cảnh nơi các biến được định nghĩa như self.apply (someObjContext, [arg1, arg2]). Trong trường hợp cụ thể này, bạn thực sự không cần nó, nhưng nếu bạn đang ở trong một thư viện, ngữ cảnh đối tượng này có thể là thứ khác ...
— loretoparisi

10

Đối với tất cả các ví dụ bên dưới, bạn cần nhập fs và mô-đun đường dẫn :

const fs = require('fs');
const path = require('path');

Đọc tệp không đồng bộ

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

Sử dụng:

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

Đọc tệp đồng bộ, lưu trữ trong mảng, sắp xếp tự nhiên

/**
 * @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;
}

Sử dụng:

// return an array list of objects
// each object represent a file
const files = readFilesSync('absolute/path/to/directory/');

Đọc tệp không đồng bộ bằng lời hứa

Thông tin thêm về quảng cáo trong bài viết này .

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

Sử dụng:

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

Lưu ý: quay lại undefinedcác thư mục, nếu muốn, bạn có thể lọc chúng ra:

readFiles('absolute/path/to/directory/')
  .then(files => files.filter(file => file !== undefined))
  .catch(err => console.log(err));

5

Bạn có phải là người lười như mình và yêu thích module npm không : D thì xem thử cái này nhé .

npm cài đặt node-dir

ví dụ để đọc tệp:

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

4

Nếu bạn có Node.js 8 trở lên, bạn có thể sử dụng use.promisify mới. (Tôi đánh dấu là tùy chọn các phần của mã liên quan đến định dạng lại dưới dạng một đối tượng, mà bài đăng gốc đã yêu cầu.)

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

3

Một phiên bản khác với phương pháp hiện đại của Promise. Điều ngắn hơn là những người khác phản hồi dựa trên Lời hứa:

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

đẹp và sắc nét! Cảm ơn @Paul
— Chaos Legion

1

Để mã hoạt động trơn tru trong các môi trường khác nhau, path.resolve có thể được sử dụng ở những nơi mà đường dẫn được thao tác. Đây là mã hoạt động tốt hơn.

Phần đọc:

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

Phần lưu trữ:

var data = {};
readFiles(path.resolve(__dirname, 'dirname/'), function(filename, content) {
  data[filename] = content;
}, function(error) {
  throw err;
});

1

Tôi vừa viết cái này và nó trông sạch sẽ hơn đối với tôi:

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

Bạn có thể sửa đổi responseđịnh dạng theo nhu cầu của mình. Các responseđịnh dạng từ mã này sẽ giống như sau:

{
   "filename-01":{
      "content":"This is the sample content of the file"
   },
   "filename-02":{
      "content":"This is the sample content of the file"
   }
}


0

async / await

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

Ai muốn thử với máy phát điện?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.