简体   繁体   中英

NodeJS Promises: Proper way to combine multiple HTTP requests returns

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.

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);
});

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM