【问题标题】:Asynchronous constructor异步构造函数
【发布时间】:2012-08-05 02:32:56
【问题描述】:

如何才能最好地处理以下情况?

我有一个需要一段时间才能完成的构造函数。

var Element = function Element(name){
   this.name = name;
   this.nucleus = {};

   this.load_nucleus(name); // This might take a second.
}

var oxygen = new Element('oxygen');
console.log(oxygen.nucleus); // Returns {}, because load_nucleus hasn't finished.

我看到了三个选项,每个选项看起来都不寻常。

一个,给构造函数添加回调。

var Element = function Element(name, fn){
   this.name = name;
   this.nucleus = {};

   this.load_nucleus(name, function(){
      fn(); // Now continue.
   });
}

Element.prototype.load_nucleus(name, fn){
   fs.readFile(name+'.json', function(err, data) {
      this.nucleus = JSON.parse(data); 
      fn();
   });
}

var oxygen = new Element('oxygen', function(){  
   console.log(oxygen.nucleus);
});

,使用 EventEmitter 来发出 'loaded' 事件。

var Element = function Element(name){
   this.name = name;
   this.nucleus = {};

   this.load_nucleus(name); // This might take a second.
}

Element.prototype.load_nucleus(name){
   var self = this;
   fs.readFile(name+'.json', function(err, data) {
      self.nucleus = JSON.parse(data); 
      self.emit('loaded');
   });
}

util.inherits(Element, events.EventEmitter);

var oxygen = new Element('oxygen');
oxygen.once('loaded', function(){
   console.log(this.nucleus);
});

或者三个,阻塞构造函数。

var Element = function Element(name){
   this.name = name;
   this.nucleus = {};

   this.load_nucleus(name); // This might take a second.
}

Element.prototype.load_nucleus(name, fn){
   this.nucleus = JSON.parse(fs.readFileSync(name+'.json'));
}

var oxygen = new Element('oxygen');
console.log(oxygen.nucleus)

但我以前从未见过这样做过。

我还有什么其他选择?

【问题讨论】:

  • 阻塞构造函数意味着阻塞一切,所以我可能不会那样做。
  • 选项 1 和 2 实际上是相同的方法。您将加载处理程序传递给构造函数,并且该处理程序从 readFile 回调中触发。这就是基于事件的编程的工作原理(这就是 JavaScript/Node 的本质)。您可以使用选项 1 或 2,或主题的任何其他变体,但实际上您会获得相同的异步行为。
  • 我从这个“问题”中学到的东西比给出的答案要多得多。为“信息性问题”+1!
  • 只有一件事。在这种情况下使用 .once() 而不是 .on()。感觉更好:)。
  • 我正在寻找一种方法来阻止 ES6 类,这可能吗?谢谢

标签: javascript node.js asynchronous constructor


【解决方案1】:

更新 2: 这是一个使用异步工厂方法的更新示例。注:如果在浏览器中运行,这需要 Node 8 或 Babel。

class Element {
    constructor(nucleus){
        this.nucleus = nucleus;
    }

    static async createElement(){
        const nucleus = await this.loadNucleus();
        return new Element(nucleus);
    }

    static async loadNucleus(){
        // do something async here and return it
        return 10;
    }
}

async function main(){
    const element = await Element.createElement();
    // use your element
}

main();

更新: 下面的代码获得了几次投票。但是,我发现使用静态方法的这种方法要好得多: https://stackoverflow.com/a/24686979/2124586

使用 Promise 的 ES6 版本

class Element{
    constructor(){
        this.some_property = 5;
        this.nucleus;

        return new Promise((resolve) => {
            this.load_nucleus().then((nucleus) => {
                this.nucleus = nucleus;
                resolve(this);
            });
        });
    }

    load_nucleus(){
        return new Promise((resolve) => {
            setTimeout(() => resolve(10), 1000)
        });
    }
}

//Usage
new Element().then(function(instance){
    // do stuff with your instance
});

【讨论】:

  • 最优雅的。
  • 奇怪的是你的构造函数没有返回实例,它返回了一个对实例的承诺,这对于 OOP 模式来说有点混乱。
  • 我同意。这些天我可能会使用一个异步静态函数来加载数据,然后返回填充数据的实例。
  • 异步工厂方法是此类情况的最佳解决方案。构造函数应始终返回该类的实例。即使是对类实例的承诺也不够好。
【解决方案2】:

鉴于在 Node 中避免阻塞的必要性,事件或回调的使用并不奇怪(1)

稍微修改一下二,您可以将其与一合并:

var Element = function Element(name, fn){
    this.name = name;
    this.nucleus = {};

    if (fn) this.on('loaded', fn);

    this.load_nucleus(name); // This might take a second.
}

...

不过,就像您示例中的 fs.readFile 一样,核心 Node API(至少)通常遵循静态函数的模式,这些函数会在数据准备好时公开实例:

var Element = function Element(name, nucleus) {
    this.name = name;
    this.nucleus = nucleus;
};

Element.create = function (name, fn) {
    fs.readFile(name+'.json', function(err, data) {
        var nucleus = err ? null : JSON.parse(data);
        fn(err, new Element(name, nucleus));
    });
};

Element.create('oxygen', function (err, elem) {
    if (!err) {
        console.log(elem.name, elem.nucleus);
    }
});

(1) 读取 JSON 文件不会花费很长时间。如果是这样,也许存储系统的变化是为了数据。

【讨论】:

    【解决方案3】:

    我开发了一个异步构造函数:

    function Myclass(){
     return (async () => {
         ... code here ...
         return this;
     })();
    }
    
    (async function() { 
     let s=await new Myclass();
     console.log("s",s)
    })();
    
    • async returns a promise
    • arrow functions pass 'this' as is
    • 在执行 new 时可能会返回其他内容(您仍然会在此变量中获得一个新的空对象。如果您在没有 new 的情况下调用该函数。您会得到原始的 this。可能是 window 或 global 或其持有对象)。
    • 可以使用 await 返回被调用异步函数的返回值。
    • 要在普通代码中使用 await,需要使用异步匿名函数包装调用,即立即调用。 (被调用的函数返回 promise 并且代码继续)

    我的第一次迭代是:

    也许只是添加一个回调

    调用匿名异步函数, 然后调用回调。

    function Myclass(cb){
     var asynccode=(async () => { 
         await this.something1();
         console.log(this.result)
     })();
    
     if(cb)
        asynccode.then(cb.bind(this))
    }
    

    我的第二次迭代是:

    让我们尝试使用 Promise 而不是回调。 我心想:奇怪的承诺回报了承诺,它奏效了。 .. 所以下一个版本只是一个承诺。

    function Myclass(){
     this.result=false;
     var asynccode=(async () => {
         await new Promise (resolve => setTimeout (()=>{this.result="ok";resolve()}, 1000))
         console.log(this.result)
         return this;
     })();
     return asynccode;
    }
    
    
    (async function() { 
     let s=await new Myclass();
     console.log("s",s)
    })();
    

    基于回调的旧 javascript

    function Myclass(cb){
     var that=this;
     var cb_wrap=function(data){that.data=data;cb(that)}
     getdata(cb_wrap)
    }
    
    new Myclass(function(s){
    
    });
    

    【讨论】:

    • 为什么在没有任何解释的情况下被否决了?为所述问题提供可行的解决方案是一项重大尝试。如果您因为认为这种方法会导致问题而投反对票,请在 cmets 中解释您的担忧。
    【解决方案4】:

    您可以做的一件事是预加载所有核(可能效率低;我不知道它有多少数据)。如果预加载不是一个选项,我会推荐另一个,它将涉及带有缓存的回调以保存加载的核。这是这种方法:

    Element.nuclei = {};
    
    Element.prototype.load_nucleus = function(name, fn){
       if ( name in Element.nuclei ) {
           this.nucleus = Element.nuclei[name];
           return fn();
       }
       fs.readFile(name+'.json', function(err, data) {
          this.nucleus = Element.nuclei[name] = JSON.parse(data); 
          fn();
       });
    }
    

    【讨论】:

      【解决方案5】:

      这是一个糟糕的代码设计。

      主要问题是在你的实例的回调中它还没有执行“返回”,这就是我的意思

      var MyClass = function(cb) {
        doAsync(function(err) {
          cb(err)
        }
      
        return {
          method1: function() { },
          method2: function() { }
        }
      }
      
      var _my = new MyClass(function(err) {
        console.log('instance', _my) // < _my is still undefined
        // _my.method1() can't run any methods from _my instance
      })
      _my.method1() // < it run the function, but it's not yet inited
      

      因此,好的代码设计是在实例化类之后显式调用“init”方法(或者在您的情况下为“load_nucleus”)

      var MyClass = function() {
        return {
          init: function(cb) {
            doAsync(function(err) {
              cb(err)
            }
          },
          method1: function() { },
          method2: function() { }
        }
      }
      
      var _my = new MyClass()
      _my.init(function(err) { 
         if(err) {
           console.error('init error', err)
           return
         } 
         console.log('inited')
        // _my.method1()
      })
      

      【讨论】:

      • 这就是我所做的。我来这里是为了寻找更好的模式,但还没有看到。
      【解决方案6】:

      我将异步部分提取到一个流利的方法中。按照惯例,我将它们称为一起。

      class FooBar {
        constructor() {
          this.foo = "foo";
        }
        
        async create() {
          this.bar = await bar();
      
          return this;
        }
      }
      
      async function bar() {
        return "bar";
      }
      
      async function main() {
        const foobar = await new FooBar().create(); // two-part constructor
        console.log(foobar.foo, foobar.bar);
      }
      
      main(); // foo bar

      我尝试了一种包装new FooBar() 的静态工厂方法,例如FooBar.create(),但它在继承方面表现不佳。如果将FooBar 扩展为FooBarChildFooBarChild.create() 仍将返回FooBar。而我的方法new FooBarChild().create() 将返回FooBarChild,并且很容易使用create() 设置继承链。

      【讨论】:

        【解决方案7】:

        您可以通过nsynjs 同步运行带有异步函数的构造函数。下面举个例子来说明:

        index.js(主应用逻辑):

        var nsynjs = require('nsynjs');
        var modules = {
            MyObject: require('./MyObject')
        };
        
        function synchronousApp(modules) {
            try {
                var myObjectInstance1 = new modules.MyObject('data1.json');
                var myObjectInstance2 = new modules.MyObject('data2.json');
        
                console.log(myObjectInstance1.getData());
                console.log(myObjectInstance2.getData());
            }
            catch (e) {
                console.log("Error",e);
            }
        }
        
        nsynjs.run(synchronousApp,null,modules,function () {
                console.log('done');
        });
        

        MyObject.js(带有慢速构造函数的类定义):

        var nsynjs = require('nsynjs');
        
        var synchronousCode = function (wrappers) {
            var config;
        
            // constructor of MyObject
            var MyObject = function(fileName) {
                this.data = JSON.parse(wrappers.readFile(nsynjsCtx, fileName).data);
            };
            MyObject.prototype.getData = function () {
                return this.data;
            };
            return MyObject;
        };
        
        var wrappers = require('./wrappers');
        nsynjs.run(synchronousCode,{},wrappers,function (m) {
            module.exports = m;
        });
        

        wrappers.js(带有回调的慢速函数的 nsynjs 感知包装器):

        var fs=require('fs');
        exports.readFile = function (ctx,name) {
            var res={};
            fs.readFile( name, "utf8", function( error , configText ){
                if( error ) res.error = error;
                res.data = configText;
                ctx.resume(error);
            } );
            return res;
        };
        exports.readFile.nsynjsHasCallback = true;
        

        可以在此处找到此示例的完整文件集:https://github.com/amaksr/nsynjs/tree/master/examples/node-async-constructor

        【讨论】:

        • 我没有 -1 你,但提及你是 nsynjs 库的作者可能是个好主意。
        猜你喜欢
        • 2013-12-27
        • 2014-10-28
        • 2018-06-26
        • 1970-01-01
        • 2017-04-14
        • 2011-12-30
        • 2017-09-11
        相关资源
        最近更新 更多