Learn, Share, Build

207
October 12, 2017, at 6:20 PM

I have a following object in my node,js module, named data:

{
    "item_uuid": "77306c44-4175-4aee-866d-d8df89fa3ii9",
    "accounts": [{
        "accountid": "B15501",
        "quantity": 1
    },
    {
        "accountid": "S20000",
        "quantity": 1
    }]
}

I need to populate each account in accounts with a country code by passing accountid to an API, before passing the entire data for further processing.

So I loop each account in accounts and do the following:

data.accounts.forEach(function(account) {
    var clientAPI = "http://0.0.0.0:3000/" + account.accountid + "/?fields=country";
    request.get(clientAPI, function (err, response, body) {
        if (err) {
            console.log("Unable to get country code for " +
                    account.accountid + " : " + err.message);
        } else {
            var clientData = JSON.parse(body);
            account.country_code = clientData.country
        }
    })
}
// once all accounts have got country code, perform insertion into database
processData(data);

Unfortunately the call to the clientAPI is async and it does not wait for the result to be returned hence when data reaches processData, it's still without country_code.

So I am trying waterfall here:

var waterfall = require('async-waterfall');
waterfall([
    function (callback) {
        data.accounts.forEach(function(account) {
            var clientAPI = "http://0.0.0.0:3000/" + account.accountid + "/?fields=country";
            request.get(clientAPI, function (err, response, body) {
                if (err) {
                    console.log("Unable to get country code for " + account.accountid + " : " + err.message);
                } else {
                    var clientData = JSON.parse(body);
                    account.country_code = clientData.country
                }
            })
        }
        callback(null, data);
    }
], function(err, data){
    processData(data);          
})

Too bad, it still does not work, when it comes to processData(data), it still has no country_code.

What is that I am missing in using waterfall here? What else I can do alternatively to populate country_code above before processData(data)?

I must call the API to get the country_code for each account.

Answer 1

Try promises (and maybe the new await/async syntax in node 7.6+), they result in a more legible, logical flow of code.

const Promise = require('bluebird')
const request = Promise.promisifyAll(require('request'))
function getData(data){
  return Promise.each(data.accounts, function(account){
    const clientAPI = {
      method: 'GET',
      json: true,
      uri: `http://0.0.0.0:3000/${account.accountid}/?fields=country`,
    }
    return request.getAsync(clientAPI).then(function(response){
      if (!response.body.country) throw new Error('No country on '+account.accountid')
      account.country_code = response.body.country
    })
    .catch(function(err){
      console.error('Unable to get country code for '+account.accountid+' : '+err.message, err);
    })
  })
}
getData(data).then(function(){ processData(data) })

You can also use Promise.map with some concurrency rather than Promise.each which is serial and will wait for each request to complete before moving onto the next.

Not catching the error (or rethrowing err) will result in the Promise being rejected rather than just logged, which may be more useful for the program. The good thing about Promises is errors will bubble up, so you can handle errors in a more global manner at the beginning of your program/calls.

Answer 2

This would be better achieved if you were using promises, or async/await, or generator syntax.

With callbacks, use a counter variable to keep track of how many requests you're sending and wait for the counter to reach a certain value before calling processData.

let counter = 0;
data.accounts.forEach(function(account) {
var clientAPI = "http://0.0.0.0:3000/" + account.accountid + "/?fields=country";
request.get(clientAPI, function (err, response, body) {
    if (err) {
        console.log("Unable to get country code for " +
                account.accountid + " : " + err.message);
        counter++
    } else {
        var clientData = JSON.parse(body);
        account.country_code = clientData.country
        counter++
    }
})
}
// once all accounts have got country code, perform insertion into database
if (counter === data.accounts.length) processData(data);
READ ALSO
Learn, Share, Build

Learn, Share, Build

I'm using Bitcore to try and make a transaction like in this example:

211
Learn, Share, Build

Learn, Share, Build

I'm teaching myself to use the MERN stack by building my first full-stack web appI've stopped in the process, because I need someone to help me better understand how to structure it, so I don't end up wasting time and inventing my own bad practices

195
Learn, Share, Build

Learn, Share, Build

Using the nodejs version of selenium I need to enter in auth information into an alert

189
Learn, Share, Build

Learn, Share, Build

Use below codes, I successfully update an item

175