问题
I am attempting to build a file-structure index using nodeJS. I'm using the fs.readir function to iterate the files, which works fine. My problem is descending into the directory structure and returning a full object with the correct structure.
I have a simple function named identify which, when given file name "myfile.txt" will return an object {name: "myfile", type: "txt"}, which will explain that part of the function below...
My problem is that nothing is being returned when I run the indexer into the "me" variable. The console.log(results) line does return, however. This leaves me quite confused.
Any help would be greatly appreciated!
indexer =
function(directory){
Self.indexleft++;
var results = {};
Self.client.readdir(directory, function(err,fileLst){
if(err){ return; }
for(var count=0; count < fileLst.length; count++){
var ident = identify(fileLst[count]);
if(ident.type = 'dir'){
var descendant = (directory !== '') ?
directory + '\\' + ident.name : ident.name;
ident.children = indexer(descendant);
}
//directory = (directory.split('\\').pop());
results[ident.name] = ident;
}
console.log(results);
return results;
});
}
var me = indexer(''); console.log(me);
EDIT:: I've actually got something working now, though it's not quite as elegant as I'd like. Below is what I did. If anyone has a suggestion on optimizing I'd be happy to hear it!!
Newest (working) Code:
var events = require('events'),
event = new events.EventEmitter(),
setToValue = function(obj, value, path) {
path = path.split('\\');
for (i = 0; i < path.length - 1; i++)
obj = obj[path[i]];
obj[path[i]] = value;
},
identify = function(file){
var split = file.split('.'),
type = (split.length > 1) ? split.pop() : 'dir',
filename = split.join('.');
return { name: filename, type: type };
};
Indexer = function(cli,dir,callback){
this.client = cli; // File reading client
this.startDir = dir; // Starting directory
this.results = {}; // Result object
this.running = 0; // How many itterations of start() are running
this.start(dir); // Start indexing
this.monit(); // Start never returns anything, monit() checks ever 5 seconds and will fire callback if 0 itterations are running.
this.callbackDone = false; // Checks whether the callback has already been fired. Important in case of interval staggering
this.cb = callback;
}
Indexer.prototype = {
start: function(directory){
var Self = this;
Self.running++;
Self.client.readdir(directory, function(err,fileLst){
if(err){ Self.running--; return; }
for(var count=0; count < fileLst.length; count++){
var ident = identify(fileLst[count]);
var descendant = (directory !== '') ? directory + '\\' + ident.name : ident.name;
if(ident.type === 'dir'){
Self.start(descendant);
}
setToValue(Self.results, ident, descendant);
}
Self.running--;
console.log('running' + Self.running);
});
},
monit: function(){
var Self = this;
Self.intervalA = setInterval(function(){
if(Self.running < 1){
if(!Self.callbackDone){
this.callbackDone=true;
Self.cb(Self.results);
}
clearInterval(Self.intervalA);
}
}, 5000)
}
}
var ix = new Indexer(Self.client,'',function(res){
console.log("Index Complete!");
fs.writeFile(path.join(Self.localLibBase,'/index.json'), JSON.stringify(res), (err)=> {
console.log("FileWrite Complete!");
});
});
Example of returned object structure :
{
"Applications" : {
"name" : "Applications",
"type" : "dir",
"Microsoft Exchange Server 2007" : {
"name" : "Microsoft Exchange Server 2007",
"type" : "dir",
"Microsoft Exchange Server 2007 SP1" : {
"name" : "Microsoft Exchange Server 2007 SP1",
"type" : "iso"
}
}
}
}
回答1:
The result is only available asynchronously, so you are trying to output the result too soon. The inner code is only executed later.
You can solve this in many ways. A very nice solution to working with asynchronous code is using promises.
As you have a recursive call, you'll have to resolve that with promises too.
NB: Note you had a bug in the comparison with "dir": you assigned instead of comparing.
Here is how your code would look:
var indexer = function(directory) {
// return a promise object
return new Promise(function (resolve, reject) {
Self.indexleft++;
var results = {};
Self.client.readdir(directory, function(err,fileLst){
if(err) {
reject(); // promise is rejected
return;
}
// "Iterate" over file list asyonchronously
(function nextFile(fileList) {
if (!fileList.length) {
resolve(results); // promise is resolved
return;
}
var file = fileLst.shift(); // shop off first file
var ident = identify(file);
results[ident.name] = ident;
if(ident.type === 'dir'){ // There was a bug here: equal sign!
var descendant = directory !== ''
? directory + '\\' + ident.name : ident.name;
// recursively call indexer: it is again a promise!
indexer(descendant).then(function (result) {
ident.children = result;
// recursively continue with next file from list
nextFile(fileList);
});
} else {
nextFile(fileLst);
}
})(fileLst); // start first iteration with full list
});
});
};
// Call as a promise. Result is passed async to callback.
indexer('').then(function(me) {
console.log(me);
});
I made some dummy functions for your external references to make this snippet work:
// Below code added to mimic the external references -- can be ignored
var filesystem = [
"",
"images",
"images\\photo.png",
"images\\backup",
"images\\backup\\old_photo.png",
"images\\backup\\removed_pic.jpg",
"images\\panorama.jpg",
"docs",
"docs\\essay.doc",
"readme.txt",
];
var Self = {
indexLeft: 0,
client: {
readdir: function (directory, callback) {
var list = filesystem.filter( path =>
path.indexOf(directory) == 0
&& path.split('\\').length == directory.split('\\').length + (directory!=='')
&& path !== directory
).map ( path => path.split('\\').pop() );
setTimeout(callback.bind(null, 0, list), 100);
}
}
}
function identify(item) {
return {
name: item,
type: item.indexOf('.') > -1 ? 'file' : 'dir'
};
}
// Above code added to mimic the external references -- can be ignored
var indexer = function(directory) {
// return a promise object
return new Promise(function (resolve, reject) {
Self.indexleft++;
var results = {};
Self.client.readdir(directory, function(err,fileLst){
if(err) {
reject(); // promise is rejected
return;
}
// "Iterate" over file list asyonchronously
(function nextFile(fileList) {
if (!fileList.length) {
resolve(results); // promise is resolved
return;
}
var file = fileLst.shift(); // shop off first file
var ident = identify(file);
results[ident.name] = ident;
if(ident.type === 'dir'){ // There was a bug here: equal sign!
var descendant = directory !== ''
? directory + '\\' + ident.name : ident.name;
// recursively call indexer: it is again a promise!
indexer(descendant).then(function (result) {
ident.children = result;
// recursively continue with next file from list
nextFile(fileList);
});
} else {
nextFile(fileLst);
}
})(fileLst); // start first iteration with full list
});
});
};
// Call as a promise. Result is passed async to callback.
indexer('').then(function(me) {
console.log(me);
});
回答2:
It's not really obvious how you're expecting to that returned object from the code you have, but I can help you get the object nonetheless.
The shape of the object is bad because you're using filenames as keys on the object but that's wrong. Keys should be identifiers known to your program, and since filenames can be almost anything, using a filename as a key is terrible.
For example, consider if a file was named name
in your structure
{ "Applications" : {
"name" : "Applications",
"type" : "dir",
"name" : {
"name" : "name"
... } } }
Yep, it just broke. Don't worry tho, our solution won't run into such troubles.
const co = require('co')
const {stat,readdir} = require('fs')
const {extname,join} = require('path')
// "promisified" fs functions
const readdirp = path =>
new Promise ((t,f) => readdir (path, (err, res) => err ? f (err) : t (res)))
const statp = fd =>
new Promise ((t,f) => stat (fd, (err,stats) => err ? f (err) : t (stats)))
// tree data constructors
const Dir = (path, children) =>
({type: 'd', path, children})
const File = (path, ext) =>
({type: 'f', path, ext})
// your function
const indexer = function* (path) {
const stats = yield statp (path)
if (stats.isDirectory ())
return Dir (path, yield (yield readdirp (path)) .map (p => indexer (join (path,p))))
else
return File (path, extname (path))
}
This is good design because we didn't tangle directory tree building in with whatever Self.client
is. Parsing a directory and building a tree is its own thing, and if you need an Object to inherit that behaviour there are other ways to do it.
Ok let's setup a sample tree of files and then run it
$ mkdir test
$ cd test
$ mkdir foo
$ touch foo/disk.iso foo/image.jpg foo/readme.txt
$ mkdir foo/bar
$ touch foo/bar/build foo/bar/code.js foo/bar/migrate.sql
Using indexer
is easy
// co returns a Promise
// once indexer is done, you will have a fully built tree
co (indexer ('./test')) .then (
tree => console.log (JSON.stringify (tree, null, ' ')),
err => console.error (err.message)
)
Output (some \n
removed for brevity)
{
"type": "d",
"path": "./foo",
"children": [
{
"type": "d",
"path": "foo/bar",
"children": [
{ "type": "f", "path": "foo/bar/build", "ext": "" },
{ "type": "f", "path": "foo/bar/code.js", "ext": ".js" },
{ "type": "f", "path": "foo/bar/migrate.sql", "ext": ".sql" }
]
},
{ "type": "f", "path": "foo/disk.iso", "ext": ".iso" },
{ "type": "f", "path": "foo/image.jpg", "ext": ".jpg" },
{ "type": "f", "path": "foo/readme.txt", "ext": ".txt" }
]
}
If you try indexer
on a path to a file, it will not fail
co (indexer ('./test/foo/disk.iso')) .then (
tree => console.log (JSON.stringify (tree, null, ' ')),
err => console.error (err.message)
)
Output
{ "type": "f", "path": "./foo/disk.iso", "ext": ".iso" }
来源:https://stackoverflow.com/questions/39151822/js-build-object-recursively