0

I've been using Bluebird a lot recently on a HAPI API development. I've just run into my first real problem, that perhaps my understanding or naivety has me stumped.

The following code is an example of what I am facing:-

var Promise = require('bluebird'),
    stuff = require('../stuff');

module.exports = {
    getSomething: function(request, reply) {
       var p = Promise.resolve();
       p = p.then(function() {
           return db.find() //etc. etc.
       });

       p = p.then(function(resultFromPromise) {
           //problems begin here
           var data = stuff.doSomeReallyLongAndBoringFunction(resultFromPromise);
           return data;
       });

       p.then(function(data) {
           //no data here.
       });
    };
};

I've commented where the problems usually begin. the stuff.doSomeReallyLongAndBoringFunction() returns an object (using more promises concidently) and it's this object I want to access, but //no data here always fires before data returns. stuff.doSomeReallyLongAndBoringFunction() continues to run regardless and completes successfully, but after the code goes async, I don't know how to promise that function's return value back.

Can anyone offer any guidance? Please accept my apologies for any naivety in the question!

Help as always, is appreciated

NB just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself. Although, I did try return new Promise(reject, resolve) { }); manual wrap. It is simply a function that uses promises itself (successfully) to get data.

Update 1 stuff.doSomeReallyLongAndBoringFunction() is too big to post directly, but it does something like this:-

var Promise = require('bluebird'),
    rp = require('request-promise');

module.exports = {
    doSomeReallyLongAndBoringFunction: function() {
       var p = Promise.resolve();
       p = p.then(function() {
           return db.find() //etc. etc.
       });

       p.then(function() {
           rp(options).then(function(response){
               //get some data from remote location
           }).then(function(dataFromService) {
               //do some jiggery pokery with said data
               var marshalledData = dataFromService;
               db.something.create({
                   Field: 'something'
               }).exec(function(err, saved) {
                   return marshalledData;
               });
           });
       }).catch(function(err) {

       });
    };
};

Update 2 Thank you Justin for your help. Here is the actual code, perhaps this may help?

Promise.resolve()
    .then(function() {
        if(typeof utils.intTryParse(place) !== 'number') {
            return foursquare.createPlaceFromFoursquare(sso, place, request, reply);
        } else {
            return { Place: { PlaceId: place }};
        }
    }).then(function(placeObj) {
        console.log('Place set as', placeObj);  //always returns undefined, despite function actually completing after async op...
    });
dooburt
  • 3,010
  • 10
  • 41
  • 59
  • What exactly does it return then? "*returns an object (using more promises concidently)*" is not really descriptive. It would be best if you could just post its code. – Bergi Mar 17 '15 at 18:33
  • If the function is asynchronous, and has a result that one might want to access, then it *should* return a promise. Fix the function. – Bergi Mar 17 '15 at 18:34
  • 1
    I might be missing something basic, but why aren't you chaining the promises if you want them to run in sequential order? for example - `p.then( ... ).then( ... ).then( ... )` – Justin Maat Mar 17 '15 at 18:43
  • @Bergi, I have updated the question to include an example of the async function. – dooburt Mar 17 '15 at 18:46
  • @JustinMaat I assume you mean in reference to ```p = p.then()``` ? Rather than ```p.then().then()```? I had problems with the latter example, the former seems to work for me? It might be pertinent to point out that promises work throughout the app as above, but not for this async function. If you think I've done something fundamentally wrong, please say! ;) – dooburt Mar 17 '15 at 18:49
  • If `doSomeReallyLongAndBoringFunction` is `async` and doesn't return a promise, then there's nothing you can do. What does it return? – Adam Jenkins Mar 17 '15 at 18:49
  • @Adam basically an object, generated from a remote service. See Update 1 for a rough idea of what it does. It uses a request-promise to get data from a remote service, does some jiggery pokery with it and returns the object. The function works, it just happens to do it after the first call has complete -- the code has gone async in ```getSomething()``` – dooburt Mar 17 '15 at 18:52
  • You are not helping yourself get an answer to your question. The crux of the matter is that `doSomeReallyLongAndBoringFunction`, must either a) return a promise or b) accept a promise as an argument and resolve that when it gets the data. If `doSomeReallyLongAndBoringFunction` is `async` then this `var data = doSomeReallyLongAndBoringFunction(...)` will **NEVER** work because an async function cannot return the data it is asynchronously getting. – Adam Jenkins Mar 17 '15 at 18:54
  • so `foursquare.createPlaceFromFoursquare` is that `doSomeThingReallyLongAndBoring`? – Bergi Mar 17 '15 at 19:11
  • @Adam ok, thank you. So I tried changing ```doSomeReallyLong()``` to return a Promise as described in my original question, but that didn't help. If the function needs to return in a particular way, could you possibly provide an example and I will happily try it for the answer? I've followed the documentation on Bluebird and ```return new Promise(resolve, reject) { //stuff removed for brevity }``` didn't work out... – dooburt Mar 17 '15 at 19:12
  • The promise you are returning needs to be resolved with that asynchronously fetched data. `that didn't help` doesn't give us any indication of what you did or what went wrong. What foursquare library are you using? – Adam Jenkins Mar 17 '15 at 19:19

3 Answers3

3

If your doSomeReallyLongAndBoringFunction is really running asynchronously, then it doesn't make sense to run it the way you have setup.

Edit - Here's a simple explanation of the way your code looks to be running vs a refactored version. It's been simplified , so you'll need to fill in the relevant sections with your actual implementation.

var Promise = require('bluebird');
    

function myAsync() {
    setTimeout(function(){
        return 'done sleeping';
    }, 2000);
};

//The way your code is running 
Promise.resolve()
    .then(function(){
        return 'hello';
    })
    .then(function(done){
        console.log(done);
        return myAsync();   //your error is here
    })
    .then(function(done){
        console.log(done);
    });


//refactored
Promise.resolve()
    .then(function(){
        return 'hello';
    })
    .then(function(done){
        console.log(done);

        return new Promise(function(resolve) {
            setTimeout(function(){
                resolve('done sleeping');
            }, 2000);
        });
    })
    .then(function(done){
        console.log(done);
    });
Justin Maat
  • 1,965
  • 3
  • 23
  • 33
  • Thanks! Unfortunately, no dice. I have added a second Update above with *actual* code. I am following your pattern, but placeObj always returns undefined – dooburt Mar 17 '15 at 19:09
  • @dooburt just edited my answer with a more simplistic explanation of what i think is going on with your code – Justin Maat Mar 17 '15 at 19:40
  • AHA! Thank you Justin! That little piece of refactored wizardry set me on my way and I've got it working! It was far simplier than I imagined: ```return new Promise(function(resolve) { foursquare.createPlaceFromFoursquare(sso, place, request, reply, function() { resolve(); }); });```. Thank you once again +1 :) -- I do wonder if calling resolve() in a callback is correct practice however? – dooburt Mar 17 '15 at 19:47
  • @dooburt: Not really. You should be using [promisification](http://stackoverflow.com/q/22519784/1048572) instead, especially if using Bluebird. And not use the `Promise` constructor inside your actual business code. – Bergi Mar 17 '15 at 19:50
  • @Bergi that means changing the return type of ```createPlaceFromFoursquare()``` into a Promise right? As per Adam's suggestion? – dooburt Mar 17 '15 at 19:58
  • @dooburt: Yes, all all of us suggested you need to get a promise for it. – Bergi Mar 17 '15 at 20:18
1

just for clarity, stuff.doSomeReallyLongAndBoringFunction() does not return a Promise itself.

And that's your problem. As it does something asynchronous and you want to get its result, it should return a promise. In fact, that's the case for every asynchronous function, especially then callbacks! It should be something like

module.exports = {
    doSomeReallyLongAndBoringFunction: function() {
        return db.find()
//      ^^^^^^
        .then(function() {
             return rp(options).then(function(response){
//           ^^^^^^
                 //get some data from remote location
             }).then(function(dataFromService) {
                 //do some jiggery pokery with said data
                 var marshalledData = dataFromService;
                 return db.something.create({
//               ^^^^^^
                     Field: 'something'
                 }).execAsyc();
             });
         }).catch(function(err) {
         });
    }
};

Your getSomething method has the same issues, and should look like this:

var createPlace = Promise.promisify(foursquare.createPlaceFromFoursquare);
module.exports = {
    getSomething: function(request) {
        var p;
        if (typeof utils.intTryParse(place) !== 'number')
            p = createPlace(sso, place, request); // this returns a promise!
        else
            p = Promise.resolve({Place: {PlaceId: place}});

        return p.then(function(placeObj) {
//      ^^^^^^
            console.log('Place set as', placeObj);
        });
    }
};

See also these generic rules for promise development.

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

doSomeReallyLongAndBoringFunction needs to look like this:

doSomeReallyLongAndBoringFunction: function(param) {
  var resolver = Promise.defer();

  /* 
   * do some asynchronous task and when you are finished
   * in the callback, do this:
   */
        resolver.resolve(resultFromAsyncTask);
  /*
   *
   *
   */

  return resolver.promise;
}
Adam Jenkins
  • 51,445
  • 11
  • 72
  • 100