JS build object recursively

限于喜欢 提交于 2019-12-13 07:01:09

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!