webentwicklung-frage-antwort-db.com.de

Asynchroner Konstruktor

Wie kann ich am besten mit einer Situation wie der folgenden umgehen? 

Ich habe einen Konstruktor, der einige Zeit in Anspruch nimmt.

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.

Ich sehe drei Optionen, von denen jede ungewöhnlich erscheint.

Eins , füge einen Rückruf zum Konstruktor hinzu.

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

Two , benutze EventEmitter, um ein 'geladenes' Ereignis auszugeben.

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

Oder drei , den Konstruktor blockieren.

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)

Aber ich habe noch nie etwas davon gesehen. 

Welche anderen Möglichkeiten habe ich?

50
Luke Burns

Angesichts der Notwendigkeit, Blockierungen in Node zu vermeiden, ist die Verwendung von Ereignissen oder Rückrufen nicht so seltsam(1).

Mit einer leichten Bearbeitung von Two könnten Sie es mit One verbinden:

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

...

Wie in fs.readFile in Ihrem Beispiel folgen die Kern-Node-APIs (zumindest) häufig dem Muster statischer Funktionen, die die Instanz verfügbar machen, wenn die Daten bereit sind:

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) Das Lesen einer JSON-Datei sollte nicht sehr lange dauern. Wenn ja, ist vielleicht eine Änderung des Speichersystems für die Daten in Ordnung.

11

Update 2: .__ Hier ist ein aktualisiertes Beispiel, das eine asynchrone Factory-Methode verwendet. N.B. Dies erfordert Node 8 oder Babel, wenn es in einem Browser ausgeführt wird.

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

Update: Der Code wurde ein paar Mal aktualisiert. Allerdings finde ich diesen Ansatz mit einer statischen Methode viel besser: https://stackoverflow.com/a/24686979/2124586

ES6-Version mit Versprechen

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

Eine Sache, die Sie tun könnten, ist das Vorladen aller Kerne (möglicherweise ineffizient; ich weiß nicht, wie viele Daten es sind). Die andere, die ich empfehlen würde, wenn das Vorladen nicht möglich ist, würde einen Rückruf mit einem Cache zum Speichern geladener Kerne beinhalten. Hier ist dieser Ansatz:

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();
   });
}
3
Will

Dies ist ein fehlerhafter Codeentwurf.

Das Hauptproblem ist im Callback Ihrer Instanz, dass es nicht immer noch das "return" ausführt, das meine ich

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

Der gute Codeentwurf besteht also darin, die "init" -Methode (oder in Ihrem Fall "load_nucleus") explizit aufzurufen, nachdem die Klasse instanziert wurde

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

Ich habe einen asynchronen Konstruktor entwickelt:

function Myclass(){
 return (async () => {
     ... code here ...
     return this;
 })();
}

(async function() { 
 let s=await new Myclass();
 console.log("s",s)
})();
  • async gibt ein Versprechen zurück
  • Pfeilfunktionen übergeben 'this' so wie sie sind
  • es ist möglich, etwas anderes zurückzugeben, wenn Sie new ausführen (Sie erhalten immer noch ein neues leeres Objekt in dieser Variablen. Wenn Sie die Funktion ohne new aufrufen. Sie erhalten das Original. Dies ist z. B. window oder global oder ein Halteobjekt).
  • es ist möglich, den Rückgabewert der aufgerufenen async-Funktion mit waitit zurückzugeben.
  • um in normalem Code zu warten, müssen Sie die Anrufe mit einer asynchronen anonymen Funktion umschließen, die sofort aufgerufen wird. (Die aufgerufene Funktion gibt ein Versprechen zurück und der Code wird fortgesetzt)

meine erste Iteration war:

vielleicht nur einen Rückruf hinzufügen

rufen Sie eine anonyme async-Funktion auf und rufen Sie dann den Rückruf auf.

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

 if(cb)
    asynccode.then(cb.bind(this))
}

meine 2. Iteration war:

versuchen wir es mit einem Versprechen anstelle eines Rückrufs. Ich dachte mir: ein seltsames Versprechen, das ein Versprechen zurückgibt, und es hat funktioniert. .. so ist die nächste Version nur ein Versprechen.

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

callback-basiert für altes Javascript

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

new Myclass(function(s){

});
2
Shimon Doodkin

Ich extrahiere die asynchronen Anteile in eine flüssige Methode. Nach Vereinbarung rufe ich sie zusammen.

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

Ich habe eine statische Fabrik-Annäherung versucht new FooBar(), z. FooBar.create(), aber mit Erbschaft spielte es nicht gut. Wenn Sie FooBar in FooBarChild erweitern, gibt FooBarChild.create() noch eine FooBar zurück. Bei meinem Ansatz wird new FooBarChild().create() eine FooBarChild zurückgeben und es ist einfach, mit create() eine Vererbungskette aufzubauen.

0
Bill Barnes