Constructor asynchronous

¿Cómo puedo manejar mejor una situación como la siguiente?

Tengo un constructor que tarda un time en completarse.

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. 

Veo tres opciones, cada una de las cuales parece fuera de lo común.

Uno , agrega una callback al constructor.

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

Dos , use EventEmitter para emitir un evento 'cargado'.

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

O tres , bloquea el constructor.

 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) 

Pero no he visto nada de esto hecho antes.

¿Qué otras opciones tengo?

Dada la necesidad de evitar el locking en Nodo, el uso de events o devoluciones de llamada no es tan extraño (1) .

Con una ligera edición de Dos, podrías fusionarlo con Uno:

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

Sin embargo, al igual que el fs.readFile en su ejemplo, las API del nodo central (al less) a menudo siguen el patrón de funciones estáticas que exponen la instancia cuando los datos están listos:

 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) No debería tomar mucho time leer un file JSON. Si lo es, tal vez un cambio en el sistema de almacenamiento sea necesario para los datos.

Actualización 2: Aquí hay un ejemplo actualizado que usa un método de fábrica asynchronous. Nota: esto requiere Nodo 8 o Babel si se ejecuta en un browser.

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

Actualización: El código siguiente se subió un par de veces. Sin embargo, me parece mucho mejor este enfoque que utiliza un método estático: https://stackoverflow.com/a/24686979/2124586

Versión ES6 con promises

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

Una cosa que podría hacer es precargar todos los núcleos (quizás ineficiente, no sé cuántos datos contiene). El otro, que recomendaría si la precarga no es una opción, implicaría una callback con un caching para save los núcleos cargados. Aquí está ese enfoque:

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

Este es un mal layout de código.

El problema principal es en la callback de su instancia, no es todavía ejecutar el "retorno", esto es lo que quiero decir

 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 

Entonces, el buen layout del código es llamar explícitamente al método "init" (o en su caso "load_nucleus") después de instanciar la class

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

Desarrollé un constructor asíncrono

 function Myclass(){ return (async () => { ... code here ... return this; })(); } (async function() { let s=await new Myclass(); console.log("s",s) })(); 
  • async devuelve una promise
  • las funciones de flecha pasan 'esto' como está
  • es posible devolver un valor como resultado de esperar, como el retorno de la llamada function asíncrona.
  • es posible devolver algo más cuando se hace algo nuevo.
  • para usar aguarde en el código normal, necesita envolver las llamadas con una function anónima asíncrona, que se llama instantáneamente. (la function llamada devuelve promise y el código continúa)

mi primera iteración fue:

tal vez solo agregue una callback

llamar a una function asíncrona anónima, luego llamar a la callback.

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

mi segunda iteración fue:

probemos con una promise en lugar de una callback. Pensé para mí: una promise extraña que devuelve una promise, y funcionó. .. así que la próxima versión es solo una 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) })(); 

basado en callback para javascript antiguo

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

Puede ejecutar la function de constructor con funciones asíncronas sincrónicamente a través de nsynjs . Aquí hay un ejemplo para ilustrar:

index.js (lógica de aplicación principal):

 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 (definición de class con constructor lento):

 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-aware wrapper alnetworkingedor de las funciones lentas con devoluciones de llamada):

 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; 

El set completo de files para este ejemplo se puede encontrar aquí: https://github.com/amaksr/nsynjs/tree/master/examples/node-async-constructor