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