【问题标题】:Recursively create directories from a multi-level JSON (with async.js)从多级 JSON 递归创建目录(使用 async.js)
【发布时间】:2012-10-17 10:24:36
【问题描述】:

定义

File#createDirectoriesFromJSON (json, cb);

json:JSON 对象。

cb:函数。参数:error(错误)、created(布尔型,如果至少创建了一个目录则为true)。

假设 File 类包含一个名为 _path 的属性,该属性包含一个目录的路径。


用法

var json = {
    b: {
        c: {
            d: {},
            e: {}
        },
        f: {}
    },
    g: {
        h: {}
    }
};

//this._path = "."
new File (".").createDirectoriesFromJSON (json, function (error, created){
    console.log (created); //Prints: true

    //callback binded to the File instance (to "this"). Hint: cb = cb.bind (this)
    this.createDirectoriesFromJSON (json, function (error, created){
        console.log (created); //Prints: false (no directory has been created)
    });
});


结果

在“.”下json 对象中显示的目录树已创建。

./b/c/d
./b/c/e
./b/f
./b/g/h


实施

这就是我所拥有的没有 async.js:

File.prototype.createDirectoriesFromJSON = function (json, cb){
    cb = cb.bind (this);

    var created = false;
    var exit = false;

    var mkdir = function (path, currentJson, callback){
        var keys = Object.keys (currentJson);
        var len = keys.length;
        var done = 0;

        if (len === 0) return callback (null);

        for (var i=0; i<len; i++){
            (function (key, i){
                var dir = PATH.join (path, key);
                FS.mkdir (dir, function (mkdirError){
                    exit = len - 1 === i;

                    if (mkdirError && mkdirError.code !== "EEXIST"){
                        callback (mkdirError);
                        return;
                    }else if (!mkdirError){
                        created = true;
                    }

                    mkdir (dir, currentJson[key], function (error){
                        if (error) return callback (error);
                        done++;
                        if (done === len){
                            callback (null);
                        }
                    });
                });
            })(keys[i], i);
        }
    };

    var errors = [];

    mkdir (this._path, json, function (error){
        if (error) errors.push (error);
        if (exit){
            errors = errors.length === 0 ? null : errors;
            cb (errors, errors ? false : created);
        }
    });
};

出于好奇,我想使用 async.js 重写该函数。这里的问题是该函数是递归和并行的。例如,“b”文件夹与“g”并行创建。 “b/c”和“b/f”,“b/c/d”和“b/c/e”也是一样。

【问题讨论】:

    标签: javascript node.js asynchronous async.js


    【解决方案1】:

    我的尝试:

    var _path  = require('path');
    var _fs    = require('fs');
    var _async = require('async');
    
    function File() {
        this._path = __dirname + '/test';
    }
    
    File.prototype.createDirectoriesFromJSON = function(json, cb) {
        var created = [], errors  = [];
    
        function iterator(path, currentJson, key, fn){
            var dir = _path.join(path, key);
    
            _fs.mkdir(dir, function(mkdirError) {
    
                if(mkdirError && mkdirError.code !== "EEXIST") {
                    errors.push(mkdirError);
                } else if(!mkdirError) {
                    created.push(dir);
                }
    
                mkdir(dir, currentJson[key], fn);
            });
        }
    
        function mkdir(path, currentJson, callback) {
            var keys = Object.keys(currentJson);
    
            if(keys.length === 0) return callback(null);
    
            _async.forEach(keys, iterator.bind(this, path, currentJson), callback);
        }
    
        mkdir(this._path, json, cb.bind(this, errors, created));
    };
    
    
    new File().createDirectoriesFromJSON({
        b: {
            c: {
                d: {},
                e: {}
            },
            f: {}
        },
        g: {
            h: {}
        }
    }, function(errors, successes) {
        // errors is an array of errors
        // successes is an array of successful directory creation
        console.log.apply(console, arguments);
    });
    

    测试:

    $ rm -rf test/* && node test.js && tree test
    
    [] [ '/Users/fg/Desktop/test/b',
      '/Users/fg/Desktop/test/g',
      '/Users/fg/Desktop/test/b/c',
      '/Users/fg/Desktop/test/b/f',
      '/Users/fg/Desktop/test/g/h',
      '/Users/fg/Desktop/test/b/c/d',
      '/Users/fg/Desktop/test/b/c/e' ] null
    test
    |-- b
    |   |-- c
    |   |   |-- d
    |   |   `-- e
    |   `-- f
    `-- g
        `-- h
    
    7 directories, 0 files
    

    注意事项:

    • 由于errors.push(mkdirError); 表示无法创建目录,因此可以将return fn(null); 附加到它以停止从该分支创建目录。
    • cb 将收到第三个参数,该参数始终为 null
    • 我最好使用wrench.mkdirSyncRecursive() 来处理这种任务,或者子堆栈异步mkdirp

    [更新]使用 mkdirp 和 lodash(或下划线),代码可以更加清晰:

    var _path   = require('path');
    var _fs     = require('fs');
    
    var _async  = require('async');
    var _mkdirp = require('mkdirp');
    var _       = require('lodash'); // or underscore
    
    function File() {
        this._path = __dirname + '/test';
    }
    
    File.prototype.flattenJSON = function(json){
        function walk(path, o, dir){
            var subDirs = Object.keys(o[dir]);
            path +=  '/' + dir;
    
            if(subDirs.length === 0){
                return path;
            }
    
            return subDirs.map(walk.bind(null, path, o[dir]));
        }
    
        return _.flatten(Object.keys(json).map(walk.bind(null, this._path, json)));
    };
    
    File.prototype.createDirectoriesFromJSON = function(json, cb) {
        var paths   = this.flattenJSON(json)
        ,   created = []
        ,   errors  = [];
    
        function iterator(path, fn){
            _mkdirp(path, function(mkdirError) {
    
                if(mkdirError && mkdirError.code !== "EEXIST") {
                    errors.push(mkdirError);
                } else if(!mkdirError) {
                    created.push(path);
                }
    
                return fn(null);
            });
        }
    
        _async.forEach(paths, iterator, cb.bind(this, errors, created));
    };
    
    
    new File().createDirectoriesFromJSON({
        b: {
            c: {
                d: {},
                e: {}
            },
            f: {}
        },
        g: {
            h: {}
        }
    }, function(errors, successes) {
        // errors is an array of error
        // successes is an array of successful directory creation
        console.log.apply(console, arguments);
    });
    

    测试:

    $ rm -rf test/* && node test2.js && tree test
    
    [] [ '/Users/fg/Desktop/test/b/f',
      '/Users/fg/Desktop/test/g/h',
      '/Users/fg/Desktop/test/b/c/d',
      '/Users/fg/Desktop/test/b/c/e' ] null
    test
    |-- b
    |   |-- c
    |   |   |-- d
    |   |   `-- e
    |   `-- f
    `-- g
        `-- h
    
    7 directories, 0 files
    

    注意:

    • iterator 可以通过使用部分函数应用程序来删除,但是下划线/lodash 只支持从左到右的部分,因此我不想要求另一个库这样做。

    【讨论】:

    • 哇!非常好的例子。我也想到了第二种方法,但它比第一种方法做了更多的循环,因为它必须计算完整的路径。在errors.push(mkdirError); 之后不应该是return
    • 如果您谈论第二个示例,则不会,因为它会阻塞 async.forEach(第一次调用“fn”并出错的迭代将立即停止 forEach 并调用最终回调)跨度>
    【解决方案2】:

    不知道这是否是最好的解决方案,但我过去通过创建一个额外的“监视器”类型对象解决了这个问题。简而言之,将您的初始化更改为:

    var monitor = {
        var init = function(json, cb) {
            this.outerDirLength = Object.keys (currentJson);
            this.processedOuterDirs = 0; //track 'report progress'  calls
            this.completedOuterDirs = 0; //'report progress' calls with no errors = success
            this.errors = [];
            this.finishedCallback = cb;
        }
        var reportProgress = function(error) {
            this.processedOuterDirs++;
            if (error) this.errors.push(error);
            else this.completedOuterDirs++;
            if (this.isComplete()) this.finish();
        }
        var finish = function () {
            var errors = this.errors.length === 0 ? null : this.errors;
            this.finishedCallback(errors, errors ? false : this.completedOuterDirs);
        }
        var isComplete = function() {
            return this.processedOuterDirs == this.outerDirLength;
        }
    };
    monitor.init(json, cb);
    if (monitor.isComplete()) {
        //handle case of JSON with zero definitions
        monitor.finish();
        return;
    }
    mkdir (this._path, json, function (error){
        monitor.reportProgress(error);
    });
    

    请注意,以上内容尚未经过测试(甚至未经过测试编译),但应该可以让您了解...如果您要使 mkdir 真正异步,您可能会更改以上内容因此,在每次调用 mkdir 开始时,它会计算要创建多少个目录,并增加监视器上的预期目标,然后在创建每个目录时更新监视器。

    【讨论】:

      猜你喜欢
      • 2011-10-10
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      • 2011-07-28
      • 2016-10-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多