1

I am using the when library with Node js. I create a deffered object, place the resolve inside an encapsulated Mongoose findOne() function, and return the promise outside. But it seems my promise is always returned before the data is retrieved.

User.prototype.getProfile = function(criteria) {
    var deferred = when.defer();
    var options = {
        criteria: criteria,
        select: 'name id email'
    };
    this.User.load(options, function(err, data) {
        if (data) {
            this.name = data.name;
            this.email = data.email;
            this.id = data.id;
        } else {
            return false;
        }
        console.log(data);
        deferred.resolve();
    });
    console.log('returning promise');
    return deferred.promise;
};

Caller

User.getProfile(req.query).then(
        function success(data) {
            res.send('Hello ' + User.name);// Hello ''
        }
    );

Outputs 'returning promise' before the data

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
forty2011111
  • 115
  • 7
  • 2
    That's the expected result. Are you having any problems with the code? Note your `return false` will mean that the promise will never resolve/reject, which is bad. – loganfsmyth Mar 01 '15 at 05:14
  • Yes, that is exactly the point. If `User.load` blocked execution and sat around waiting for the query to complete, there would be no point in using promises in the first place. – JLRishe Mar 01 '15 at 06:43
  • This looks a bit like the [deferred antipattern](http://stackoverflow.com/q/23803743/1048572) - you never `reject` but simply leave the promise pending. – Bergi Mar 01 '15 at 11:14

2 Answers2

4

Yes, promise will be returned to the caller instead of the data and that is how we can take advantage of the asynchronous functions. This is the common sequence of actions in handling async calls,

  1. Make an async call.

  2. Return a Promise to the caller.

  3. At this point, caller doesn't have to wait for the result. It can simply define a then function, which knows what to do when the data is ready and move on to the next task.

  4. Later point of time, resolve (or reject, if failed) the promise when you get the result from the async call.

  5. Execute the then function on the Promise object, with the result from the async call.

So, your code will have to be modified a little bit, like this

User.prototype.getProfile = function(criteria) {
    var deferred = when.defer();
    var options = {
        criteria: criteria,
        select: 'name id email'
    };
    this.User.load(options, function(err, data) {
        if (err) {
            // Reject, if there is an error
            deferred.reject(err);
        } else {
            // Resolve it with actual data
            deferred.resolve(data);
        }
    });
    return deferred.promise;
};

Then your caller will do something like this

userObject.getProfile()
    .then(function(profileObject) {
        console.log(profileObject);
        // Do something with the retrieved `profileObject`
    })
    .catch(function(err) {
        console.err("Failed to get Profile", err);
    });

// Do something else here, as you don't have to wait for the data

Here, caller just calls getProfile and attaches a function which says what to do with the returned data and moves on.


Edit If you want the same object to be updated, then you can simply use similar code, but you need to preserve this in some other variable, because the binding of this happens at runtime.

User.prototype.getProfile = function(criteria) {
    var deferred = when.defer();
    var options = {
        criteria: criteria,
        select: 'name id email'
    };
    var self = this;
    this.User.load(options, function(err, data) {
        if (err) {
            // Reject, if there is an error
            deferred.reject(err);
        } else {
            self.name = data.name;
            self.email = data.email;
            self.id = data.id;
        }
        deferred.resolve(data);
    });
    return deferred.promise;
};
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
2

That's how promises work.

Since you have an async task that takes some time, and JavaScript is a single threaded language, you don't want to block your code and wait for that async operation to complete itself - otherwise nobody would use JavaScript!!

So what do you do? You create a promise and continue your code.
You add callbacks to that promise and when the promise is resolved your callbacks are invoked.

I didn't use the when library but what you want to do is something like this:

User.prototype.getProfile = function(criteria){
    var deferred = when.defer();
    var options = {
        criteria : criteria,
        select : 'name id email'
    };
    this.User.load(options, function(err, data) {
        if (data) {
            this.name = data.name;
            this.email = data.email;
            this.id = data.id;
            console.log(data);
             // the callback will invoke after the deferred object is resolved.
             deferred.promise.then(function(o){ console.log('resolved!!!'); });
             deferred.resolve(data);
        }else{
            deferred.reject('something bad occured');
            return false;
        }

    });

    return deferred.promise;
};
Amir Popovich
  • 29,350
  • 9
  • 53
  • 99
  • Your code has the same problem as OP's data, in that the promise will never resolve if `data` is falsy. – JLRishe Mar 01 '15 at 06:37