Comment puis-je gérer au mieux une situation comme celle-ci?
J'ai un constructeur qui prend un certain temps à compléter.
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.
Je vois trois options, chacune d’entre elles sortant de l’ordinaire.
One , ajoute un rappel au constructeur.
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 , utilisez EventEmitter pour émettre un événement "chargé".
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);
});
Ou trois , bloque le constructeur.
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)
Mais je n'ai jamais rien vu de tel auparavant.
Quelles autres options ai-je?
Compte tenu de la nécessité d'éviter le blocage dans le nœud, l'utilisation d'événements ou de rappels n'est pas si étrange(1).
Avec une légère modification de Two, vous pouvez le fusionner avec One:
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.
}
...
Cependant, comme le fs.readFile
dans votre exemple, les API de nœud de base (ou au moins) suivent souvent le modèle de fonctions statiques qui expose l'instance lorsque les données sont prêtes:
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) La lecture d’un fichier JSON ne devrait pas être très longue. Si c'est le cas, un changement de système de stockage est peut-être nécessaire pour les données.
Mise à jour 2: Voici un exemple mis à jour utilisant une méthode de fabrique asynchrone. N.B. cela nécessite le nœud 8 ou Babel s'il est exécuté dans un navigateur.
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();
Mise à jour: Le code ci-dessous a été voté plusieurs fois. Cependant, je trouve cette approche beaucoup mieux d'utiliser une méthode statique: https://stackoverflow.com/a/24686979/2124586
Version ES6 utilisant des promesses
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
});
Une chose que vous pouvez faire est de précharger tous les noyaux (peut-être inefficace; je ne sais pas combien de données il contient). L'autre, que je recommanderais si le préchargement n'est pas une option, impliquerait un rappel avec un cache pour sauvegarder les noyaux chargés. Voici cette approche:
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();
});
}
Ceci est une mauvaise conception de code.
Le problème principal réside dans le rappel de votre instance qui n’exécute pas toujours le "retour", c’est ce que je veux dire.
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
Ainsi, la bonne conception de code consiste à appeler explicitement la méthode "init" (ou dans votre cas, "load_nucleus") après instance de la classe
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()
})
J'ai développé un constructeur asynchrone:
function Myclass(){
return (async () => {
... code here ...
return this;
})();
}
(async function() {
let s=await new Myclass();
console.log("s",s)
})();
ma 1ère itération était:
peut-être juste ajouter un rappel
appeler une fonction asynchrone anonyme, puis appeler le rappel.
function Myclass(cb){
var asynccode=(async () => {
await this.something1();
console.log(this.result)
})();
if(cb)
asynccode.then(cb.bind(this))
}
ma 2ème itération était:
essayons avec une promesse au lieu d'un rappel. Je me suis dit: une promesse étrange qui rendait une promesse, et cela a fonctionné. .. alors la prochaine version est juste une promesse.
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 basé sur javascript ancien
function Myclass(cb){
var that=this;
var cb_wrap=function(data){that.data=data;cb(that)}
getdata(cb_wrap)
}
new Myclass(function(s){
});
J'extrais les portions asynchrones dans une méthode fluide. Par convention, je les appelle ensemble.
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
J'ai essayé une approche d'usine statique en enveloppant new FooBar()
, par exemple. FooBar.create()
, mais cela ne fonctionnait pas bien avec l'héritage. Si vous étendez FooBar
en FooBarChild
, FooBarChild.create()
renverra toujours un FooBar
. Alors qu'avec mon approche, new FooBarChild().create()
renverra une FooBarChild
et il est facile de configurer une chaîne d'héritage avec create()
.