CoffeePeddlerIntern
CoffeePeddlerIntern

Reputation: 679

Making Multiple Http Calls and Consolidating Results with Promises

Ok I have found similar problems answered to my problem but not exactly.

Examples:

Nodejs making parallel http calls and consolidate the result

Node.js - wait for multiple async calls

I have 8 functions the do the same thing except hit a different api endpoint. In those functions I look at the response and pick out what I want and return. Then I have a function that uses bluebird to make all the calls at once and wait for their responses.

This is what 1 of 8 functions look like (pretty much just swap out the api it is hitting).

function getMorphite(){
request.get(crestMarketHistoryMorphite , function (error, response, body) {
    if (!error && response.statusCode === 200) {
        var content = JSON.parse(body);
        var topMorphiteBuy;
        var maxBuy = 0;
        for(var i = 0; i < content.items.length; i++){
            var item = content.items[i];
            if(item.location.id == 60003760){
                var buyPrice = item.price;
                if(buyPrice > maxBuy){
                    maxBuy = buyPrice;
                    topMorphiteBuy = item;
                }
            }
        }
        console.log(topMorphiteBuy);
        return topMorphiteBuy
    } else {
        console.error(error);
        return error;
    }
});
}

And here is my function grabbing all of my functions, running them, and grabbing their responses.

exports.getSearchResults = function (req, res) {
var allItems = [];
Promise.all([getTritanium(), getPyrite(), getMexallon(), getIsogen(), getNocxium(), getZydrine(), getMegactye(), getMorphite()]).then(function (response) {
    console.log("Response", response);
    allItems.push(response[0]);
    allItems.push(response[1]);
    allItems.push(response[2]);
    allItems.push(response[3]);
    allItems.push(response[4]);
    allItems.push(response[5]);
    allItems.push(response[6]);
    allItems.push(response[7]);
    res.type('application/json');
    res.json(allItems);
});
};

So this 'works' but im having some return problems. Right now the response in is an array of 8 items of undefined. What the heck am I missing here. It has to be a return problem but I feel like I have panic switched around all return statements with no luck.

Upvotes: 0

Views: 2744

Answers (1)

jfriend00
jfriend00

Reputation: 707158

Promise.all() only waits for the async operations if you pass it an array of promises where each promise is linked to a corresponding operation. Right now, you're passing it an array of undefined values because your getMorphite() function and the others like it do not actually return anything. The return statements from inside the ``request.get()callback happen aftergetMorphite()has already long since returned and those return values just go back into the bowels of therequest()` implementation and are ignored.

Because you're passing Promise.all() an array of undefined values, it doesn't wait for anything and it doesn't get any results. So, it calls its .then() handler immediately and has nothing but the array of undefined values for its results.

If you want to use Promise.all() for this (it is the right right tool for the job), then you need to "promisify" your request() operations so they return a promise that is resolved with the final result when the async operation is done. Then, Promise.all() will be given an array of promises and Promise.all().then() will wait for all the promises to be done and will be given an array of results from those promises.

If you're using Bluebird, you can use it to Promisify the request.get() operation like this:

 var request = Promise.promisifyAll(request, {multiArgs: true});

This "promisifyAll" step adds new methods to the request object with the "Async" suffix so request.get() gets a companion method named request.getAsync(). That new "Async" version returns a promise. The multiArgs option is not usually needed, but in this case since request.get() returns more than one data argument (which is not the usual node async calling convention), it is needed here.

And, here's an implementation of getMorphite() that returns a promise and returns the eventual value as the fulfilled value of the promise.

function getMorphite() {
    return request.getAsync(crestMarketHistoryMorphite).spread(response, body) {
        if (response.statusCode === 200) {
            var content = JSON.parse(body);
            var topMorphiteBuy;
            var maxBuy = 0;
            for (var i = 0; i < content.items.length; i++) {
                var item = content.items[i];
                if (item.location.id == 60003760) {
                    var buyPrice = item.price;
                    if (buyPrice > maxBuy) {
                        maxBuy = buyPrice;
                        topMorphiteBuy = item;
                    }
                }
            }
            console.log(topMorphiteBuy);
            // return this to make it the fulfilled value of the promise
            return topMorphiteBuy;    
        } else {
            // reject the promise with bad status code
            throw new Error("response.statusCode was: " + response.statusCode)
        }
    });
}

If you change the other functions to also work this way, you can then use your original code like this:

Promise.all([getTritanium(), getPyrite(), getMexallon(), getIsogen(), 
             getNocxium(), getZydrine(), getMegactye(), getMorphite()])
  .then(function (response) {
    console.log("Response", response);
    allItems.push(response[0]);
    allItems.push(response[1]);
    allItems.push(response[2]);
    allItems.push(response[3]);
    allItems.push(response[4]);
    allItems.push(response[5]);
    allItems.push(response[6]);
    allItems.push(response[7]);
    res.type('application/json');
    res.json(response);
});

Upvotes: 2

Related Questions