0

Sorry i couldn't come up with a straight forward question/title, but here's the thing. I have the following construct:

var userProvider=function(login){
   this.getUser=mongoose.find(login).exec();
}
userProvider.prototype.doStuffWithUserData=function(){
   this.getUser.then(function(user){
       ...
   });
} 
userProvider.prototype.doOtherStuffWithUserData=function(){
   this.getUser.then(function(user){
       ...
   });
} 

Is there a a nicer way, such that i don't need to call the promise in every prototype function, though the prototype functions being evaluated only until the data is there?

user3434845
  • 49
  • 2
  • 8
  • 1
    If you're repeating it you can extract it into a decorator. – Benjamin Gruenbaum Oct 02 '14 at 21:32
  • possible duplicate of [Is it bad practice to have a constructor function return a Promise?](http://stackoverflow.com/q/24398699/1048572) - if that helps to solve your problem I'll gladly close as a dupe, if not I can post an answer with the decorator technique that Benjamin mentioned. – Bergi Oct 02 '14 at 22:02
  • i'd go with the bad practice case, thanks :) – user3434845 Oct 03 '14 at 11:09
  • @Bergi His constructor doesn't return a promise though. – Benjamin Gruenbaum Oct 03 '14 at 12:19
  • @BenjaminGruenbaum: You're right, but it seems to be same setup - methods are relying asynchronous sideeffects done in the constructor. Sure, we don't know what `doStuff` and `doOtherStuff` exactly is, but they better not try to manipulate the instance or call other methods; otherwise this class would get a complete mess. – Bergi Oct 03 '14 at 12:24

2 Answers2

1

Here is how you can do it with decoration. This is tricky because the promise is not available when you create the function yet:

function whenReady(fnToRun){
    return function runsWhenReady(){ // the decorated function
        // keep track of arguments
        var args = Array(arguments.length + 1); // inline slice is much faster
        for(var i = 0; i < arguments[i]; i++) args[i+1] = arguments[i];
        return this.getUser.then(function(user){
            args[0] = user;
            return fnToRun.call(this, args); // append user and call with args
        }.bind(this)); // maintain context)
    };
}

This would let you do:

userProvider.prototype.doStuffWithUserData = whenReady(function(user){
    // user is available here 
});

myUserProviderInstance.doStuffWithData(); // inside the call user is available

userProvider.prototype.otherStuff = whenReady(function(user, x){
    // user is available here 
});

myUserProvider.otherStuff(15); // x will be 15 and user will be available, promise returned

This approach can be generalized to a more general whenReady that takes a "waitFor" argument. It's also worth mentioning that if you use a library like Bluebird it already ships with bind and method which let you do this in a more easy way.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 1
    You could do `var args = new Array(1)` so that you can avoid the `concat` call and do an assignment instead, if you really want to aim for speed :-) – Bergi Oct 03 '14 at 12:34
1

Is there a a nicer way, such that the prototype functions being evaluated only when the data is there?

I'd recommend not to execute the find sideeffect in the constructor and create the promise there, but get the result of the mongoose.find(login).exec() passed into the constructor, as laid out in Is it bad practice to have a constructor function return a Promise?

I'd go with the bad practice case

To avoid the repetition, you can create your prototype methods like this:

function userProviderMethod(n, m) {
    UserProvider.prototype[n] = function() {
        var args = Array.prototype.slice.call(arguments),
            that = this;
        return this.getUser.then(function(user) {
            args.unshift(user);
            return m.apply(that, args);
        });
    };
}

function UserProvider(login){
    this.getUser = mongoose.find(login).exec();
}
userProviderMethod("doStuffWithUserData", function(user /*, … */) {
    // …
});
userProviderMethod("doOtherStuffWithUserData", function(user) {
    // …
});

Notice that none of these "methods" will actually ever get executed when .getUser did get rejected.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375