0

Have a NodeJS process that reaches out to a webservice for something called Kudos. These kudos are sent from one person to another person/or group of people. What I'm trying to do is create one message that has the following:


Kudos from {poster} to {receiver/s}

{Kudos Message}


Currently I have the process working correctly for poster to one receiver. I am struggling with making it work with getting the multiple names of the receivers.

The problem stems from the fact that the section where it returns the users receiving the kudos, it only provides the user id. So I need to make another call to obtain the user's name. I can easily get the promises to work for the one user, but I can seem to get the multiple user properly.

The JSON data that contains the multiple users looks something like this:

 "notes_user": [
  {
    "id": "1060",
    "note_id": "795",
    "user_id": "411"
  },
  {
    "id": "1061",
    "note_id": "795",
    "user_id": "250"
  },
  {
    "id": "1062",
    "note_id": "795",
    "user_id": "321"
  }
],

Here is the function that does the majority of the work:

getMaxId returns a database index of that highest kudos currently processed, and getKudos just returns the json dataset of "kudos".

function promisifiedKudos() {
var maxid;
var newmaxid;

Promise.all([getMaxId(), getKudos()])
    .then(function(results) {
        maxid = results[0];

        var kudos = results[1];
        newmaxid = kudos[0].id;
        return kudos.filter(function(kudo) {
            if (maxid < kudo.id) {
                return kudo;
            }
        })
    })
    .each(function(kudo) {
        return getTribeUserName(kudo);
    })
    .then(function(results) {
        return results.map(function(kudo) {
            var message = "Kudos from " + kudo.poster.full_name + " to " + kudo.kudo_receiver_full_name + "\r\n";
            message += "\r\n";
            return message += entities.decode(striptags(kudo.note));
        })
    })
    .each(function(message) {
        return postStatus(message);
    })
    .then(function() {
        var tribehr = db.get('tribehr');
        console.log(new Date().toString() + ":Max ID:" + newmaxid);
        tribehr.update({ endpoint: "kudos" }, { $set: { id: newmaxid } });
    })
    .done(function(errors) {
        console.log("Run Complete!");
        return "Done";
    });
}

The helper function getTribeUserName()

function getTribeUserName(kudo) {
return new Promise(function(fulfill, reject) {
    var id = kudo.notes_user[0].user_id;
    var options = {
        url: "https://APIURL.com/users/" + id + ".json",
        method: "GET",
        headers: {
            "Authorization": "Basic " + new Buffer("AUTHCODE" + AUTHKEY).toString('base64')
        }
    }
    request.getAsync(options).then(function(response) {
        if (response) {
            var data = JSON.parse(response.body)
            kudo.kudo_receiver_full_name = data.User.full_name;
            fulfill(kudo);
        } else {
            reject("Get Tribe User Name Failed");
        }
    });
});
}

I've tried adding a helper function that calls the getTribeUserName() that looks like this:

function getTribeUsers(kudo) {
return new Promise(function(fulfill, reject) {
    kudo.notes_user.map(function(user) {
        //Make calls to a getTribeUserName
    })
});
}

But the outcome is that the user names are undefined when the finalized message is put together.

Any pointers in how to use promises better would be extremely helpful. This is really my first stab with them and I hope I'm heading in the right direction. I know I need to add the error checking in, but currently I'm just trying to get the process working for multiple users.

ChrisRiv91
  • 91
  • 3
  • What Node version are you using? Also, what Promise library? `.each()` and `.done()` aren't standard functions. – Madara's Ghost Nov 09 '16 at 20:33
  • _"I can easily get the promises to work for the one user"_ Repeat the process for each user? – guest271314 Nov 09 '16 at 20:40
  • Have you thought about using events instead? Each 'receiver' could trigger an event which would do the work. – ppovoski Nov 09 '16 at 20:44
  • @guest271314 I don't know how many users are going to be coming back for the request. So I need to make the function handle both cases correctly – ChrisRiv91 Nov 09 '16 at 21:12
  • @MadaraUchiha I'm using Bluebird and Node 4.4.4 – ChrisRiv91 Nov 09 '16 at 21:13
  • @ChrisRiv91 Yes. Perform same procedure until no user id's are returned, else return array of accumulated results of procedures. See [multiple, sequential fetch() Promise](http://stackoverflow.com/questions/38034574/multiple-sequential-fetch-promise/) – guest271314 Nov 09 '16 at 21:25
  • @guest271314 Looking into what you shared, thanks! – ChrisRiv91 Nov 09 '16 at 21:27

2 Answers2

0

if you need to use the result of a promise passed as parameter of the resolve function then you can catch it in the then onFulfilled callback.

If you need to pass some data obtained within a then method of a chain to another then you just have to return it and catch it through the onFulfilled callback of the following then.

object.somePromise().then(function(param){
    var data = someFunction();
    return data;
}).then(function(param){
    //param holds the value of data returned by the previous then
    console.log(param);
});
Andrea Sindico
  • 7,358
  • 6
  • 47
  • 84
0

If it's a matter of getting multiple TribeUserNames asynchronously, then you need somehow to aggregate promises returned by multiple calls to getTribeUserNames().

You could write Promise.all(array.map(mapper)) but Bluebird provides the more convenient Promise.map(array, mapper).

Bluebird's .spread() is also convenient, for referencing maxid and kudos.

Here it is in as simple a form as I can manage :

function promisifiedKudos() {
    return Promise.all([getMaxId(), getKudos()])
    .spread(function(maxid, kudos) {
        var newmaxid = kudos[0].id;
        // The following line filters (synchronously), adds TribeUserNames (asynchronously), and delivers an array of processed kudos to the next .then().
        return Promise.map(kudos.filter((kudo) => kudo.id > maxid), getTribeUserName)
        .then(function(filteredKudosWithTribeUserNames) { // in practice, shorten arg name to eg `kudos_`
            return Promise.map(filteredKudosWithTribeUserNames, function(kudo) {
                return postStatus("Kudos from " + kudo.poster.full_name + " to " + kudo.kudo_receiver_full_name + "\r\n\r\n" + entities.decode(striptags(kudo.note)));
            });
        })
        .then(function() {
            var tribehr = db.get('tribehr');
            console.log(new Date().toString() + ":Max ID:" + newmaxid);
            return tribehr.update({ endpoint: 'kudos' }, { $set: { 'id': newmaxid } });
        });
    })
    .then(function() {
        console.log('Run Complete!');
    }).catch(function(error) {
        console.log(error);
        throw error;
    });
}

getTribeUserName() needs to return a promise, and can be written as follows :

function getTribeUserName(kudo) {
    var options = {
        'url': 'https://APIURL.com/users/' + kudo.notes_user[0].user_id + '.json',
        'method': 'GET',
        'headers': {
            'Authorization': 'Basic ' + new Buffer('AUTHCODE' + AUTHKEY).toString('base64')
        }
    }
    return request.getAsync(options).then(function(response) {
//  ^^^^^^
        if(response) {
            kudo.kudo_receiver_full_name = JSON.parse(response.body).User.full_name;
        } else {
            throw new Error(); // to be caught immediately below.
        }
        return kudo;
    }).catch(function(error) { // error resistance
        kudo.kudo_receiver_full_name = 'unknown';
        return kudo;
    });
}

Further notes:

  • By nesting Promise.map(...).then(...).then(...) in the .spread() callback, newmaxid remains available through closure, avoiding the need for an ugly outer var.
  • Promise.map() is used a second time on the assumption that postStatus() is asynchronous. If that's not so, the code will still work, though it could be written slightly differently.
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44