coding 7891
coding 7891

Reputation: 53

Making an array of object which have an asynchronous constructor

I want to do the following with NodeJS. Make an object array of the following, where each object has different local variables where they want to get when they initialize.

obj.js

var Obj = function (_id) {
    this.id = _id;
    var that=this;
    db.getData(_id,function(collection){ //this method is asynchronous 

        collection.toArray(function(err, items) {
            that.data=items;
        });
    });
}

Obj.prototype.data = [];  

module.exports = Obj;

app.js

var arr=[];
arr.push(new obj(24));
arr.push(new obj(41));
arr.push(new obj(24));
arr.push(new obj(42));
//then do tasks with the arr

But since the arr constructor is synchronous they may not have get all the data when I do calculations with the arr. So how to handle this scenario ? I want to make sure that all the objects are successfully created before doing any work with them.

Thanks in advance.

Upvotes: 2

Views: 80

Answers (2)

Mike
Mike

Reputation: 349

guy, what @mscdex said is correct. Firstly, in your code, data will be shared in memory, you should use this.data=[] in constructor. Secondly, as @mscdex said, move your method into prototype, say

Obj.prototype.load=function(){
    //code here...
}

then your code my like below:

var Obj = function(_id){
    this.id=_id;
    this.data = [];
}
Obj.prototype.load = function(){
    var that = this;
    db.getData(this.id,function(collection){ //this method is asynchronous
        collection.toArray(function(err, items) {
            that.data=items;
        });
    });
    return that;
}

finally, your question, how do you know all of them are ready.

Obj.prototype.ready = [];
Obj.prototype.loaded=function(){
    this.ready.push(1);
    if(this.ready.length == Obj.target)
        Obj.onComplete();
}
Obj.notifyme = function(callback,len){
    Obj.target = len;
    Obj.onComplete = callback;
}

the above code set an array to count load complete instances(use array because basic value could not be read from an instance's __proto__ reference). So what you should do is add this event(function) to load, so the code at last may be as following:

Obj.prototype.load = function(){
    var that = this;
    db.getData(this.id,function(collection){ //this method is asynchronous
        collection.toArray(function(err, items) {
            that.data=items;
            that.loaded();
        });
    });
    return that;
}
var args = [24,41,42];
Obj.notifyme(function(){/*then do tasks with the arr*/},args.length);
var arr = args.map(function(arg){
    return new Obj(arg).load();
});

tell function notifyme the callback work and the number of instances. The last problem is if you do this routine more than one times you should reset target and callback since they are Obj global things.

Upvotes: 2

dfsq
dfsq

Reputation: 193291

I would make use of Promise pattern. Here is prototype of such an approach. I used native browser Promise for implementation and testing. In case of NodeJS you can use Kowal's Q implementation with similar (or very close) API.

var Obj = function (_id) {
    this.id = _id;
    var that = this;

    this.promise = function() {
        return new Promise(function(resolve) {
            db.getData(_id, function (collection) {
                collection.toArray(function (err, items) {
                    that.data.push(items);
                    resolve(that);
                });
            });
        });
    }();
};

Obj.prototype.data = [];


function objFactory(args) {
    var promises = args.map(function(el) {
        return new Obj(el).promise;
    });
    return Promise.all(promises);
}

objFactory([24, 25, 41, 42]).then(function(arr) {
    console.log(arr, arr[0].data);
});

The idea is that when the promise object returned by objFactory resolves it will invoke corresponding then callback, inside of which it's reliable to work with data since it's populated for sure by that time.

Here is browser demo I was testing code with. You will have to adopt it for NodeJS environment. But the idea remains the same.

For example using Kris Kowal's Q it will be something like this:

this.promise = function() {
    var deferred = Q.defer();
    db.getData(_id, function (collection) {
        collection.toArray(function (err, items) {
            that.data.push(items);
            deferred.resolve(that);
        });
    });
    return deferred.promise;
}();

and Q.all instead of Promise.all.

Upvotes: 1

Related Questions