How can I best handle a situation like the following?
I have a constructor that takes a while to complete.
var Element = function Element(name){
Update 2: Here is an updated example using an asynchronous factory method. N.B. this requires Node 8 or Babel if run in a 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();
Update: The code below got upvoted a couple of times. However I find this approach using a static method much better: https://stackoverflow.com/a/24686979/2124586
ES6 version using 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
});
I have developed an async constructor:
function Myclass(){
return (async () => {
... code here ...
return this;
})();
}
(async function() {
let s=await new Myclass();
console.log("s",s)
})();
my 1st iteration was:
maybe just add a callback
call an anonymous async function, then call the callback.
function Myclass(cb){
var asynccode=(async () => {
await this.something1();
console.log(this.result)
})();
if(cb)
asynccode.then(cb.bind(this))
}
my 2nd iteration was:
let's try with a promise instead of a callback. I thought to myself: strange a promise returning a promise, and it worked. .. so the next version is just a 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)
})();
callback-based for old javascript
function Myclass(cb){
var that=this;
var cb_wrap=function(data){that.data=data;cb(that)}
getdata(cb_wrap)
}
new Myclass(function(s){
});
This is a bad code design.
The main problem is in the callback your instance it's not still execute the "return", this is what I mean
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
So, the good code design is to explicitly call the "init" method (or in your case "load_nucleus") after instanced the 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()
})
I extract out the async portions into a fluent method. By convention I call them together.
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
I tried a static factory approach wrapping new FooBar()
, e.g. FooBar.create()
, but it didn't play well with inheritance. If you extend FooBar
into FooBarChild
, FooBarChild.create()
will still return a FooBar
. Whereas with my approach new FooBarChild().create()
will return a FooBarChild
and it's easy to setup an inheritance chain with create()
.
One thing you could do is preload all the nuclei (maybe inefficient; I don't know how much data it is). The other, which I would recommend if preloading is not an option, would involve a callback with a cache to save loaded nuclei. Here is that approach:
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();
});
}
Given the necessity to avoid blocking in Node, the use of events or callbacks isn't so strange(1).
With a slight edit of Two, you could merge it with 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.
}
...
Though, like the fs.readFile
in your example, the core Node APIs (at least) often follow the pattern of static functions that expose the instance when the data is ready:
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) It shouldn't take very long to read a JSON file. If it is, perhaps a change in storage system is in order for the data.