0

I am trying to get my nodejs controller to update the rate in the currency table.

Everything works fine in S3T / RoboMongo, but for some reason it just wont fire the update inside the nodejs controller.

Here is my currency table

{ 
    "_id" : "USD", 
    "index" : NumberInt(6), 
    "name" : "Dollar", 
    "currency" : "USD", 
    "symbol" : "$", 
    "active" : true, 
    "default" : false,
    "rate" : 0
}
{ 
    "_id" : "EUR", 
    "index" : NumberInt(2), 
    "name" : "Euro", 
    "currency" : "EUR", 
    "symbol" : "€", 
    "active" : true, 
    "default" : false,
    "rate" : 0  
}

I tried both of these, works fine in S3T but not inside nodejs:

db.currency.update (
    { _id : "EUR" },
    { $set: { rate : 123 }},
    { upsert: true }
)

db.currency.updateOne (
    { _id : "EUR" },
    { $set: { rate : 123 }},
    { upsert: true }
)

Here is my nodejs code:

var mongoose = require('mongoose');
var currencyModel = require('../models/currencyModel');
var currencyTable = mongoose.model('currencyModel');
var updateRates = () => {
    return new Promise((resolve, reject) => {
        for (var key in data.quotes) {
            var currencyID = key.substring(3);
            var newRate = (data.quotes[key] * THBUSD).toFixed(5);
            console.log("currencyID: " + currencyID)
            console.log("newRate: " + newRate)

            currencyTable.update (
                { _id: currencyID },
                { $set: { rate : newRate }},
                { upsert: true }                    
            ),function (err, data) {
                if (err) {
                    reject(new Error('updateRates: ' + err));
                };
            };                      
        };
       resolve();
   })};

And here is my currencyModel (which is where I think the problem is?!?)

// Currency Model
// This model is the structure containing data from the Currency table 
//
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var currencySchema = new Schema({
    _id:                String,     // Unique Currency code
    index:              Number,     // Indes for sorting
    name:               String,     // Currency name 
    symbol:             String,     // Currency symbol 
    active:             Boolean,    // Active True False
    rate:               Number      // Exchange rate (multiply with THB price)
});
module.exports = mongoose.model('currencyModel', currencySchema, 'currency');

I cannot see why it wont fire the currencyTable.update from inside nodejs.

I turned debug on in mongoose, and I see all other mongodb operations in the console like Mongoose: price.findOne({ _id: 'ATL-D406' }, { fields: {} }) etc.. but I do not see this currency.update in the console, which is why I dont think its fired off to mongodb - and I cannot see the reason.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
torbenrudgaard
  • 2,375
  • 7
  • 32
  • 53

1 Answers1

1

You have a "loop" that completes execution before the inner callbacks fire. Instead just use Promises all the way through and call Promise.all() to collect all the iterated Promises and resolve them:

var updaterates = () => {
  return Promise.all(
    Object.keys(data.quotes).map(k => {
      return currencyTable.update(
        { _id: k.substring(0,3) },
        { $set: { rate : (data.quotes[k] * THBUSD).toFixed(5) }},
        { upsert: true }        
      ).exec()
    });
  )
};

The returned response of Promise.all() is an array of the response objects from the updates. Also note that this is a "fast fail" operation and calls will be made in parallel.

Object.keys() returns an "array of the key names" in the specified object. .map() iterates those keys and returns an "array" of the return value for the iterator.

We use the k as the "key name" to access the wanted key from data.quotes and use the values to perform each .update() with .exec() to return a "real" Promise. The iterator returns an "array" Promise which becomes the argument to Promise.all().

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • Neil you are very good at this!! You used map instead!! - I didn't know about the .exec() command either. So what you do here is `Object.keys(data.quotes).map` is that like saying `for k=0;k – torbenrudgaard Jun 21 '17 at 13:24
  • 1
    @torbenrudgaard There's plenty of documentation readily available and they all come up in google if you just type in their names. I've added all of those to the answer. Basically put *"get the list of keys and iterate over that list, to return a "different" list. Hence `Object.keys().map()`. Get list. Transform. – Neil Lunn Jun 21 '17 at 13:47
  • 1
    @torbenrudgaard By all rights I could have closed your question as a duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/q/14220321/2313887) because you were basically calling `.update()` inside a loop without waiting for the calls to complete before exiting the loop. But I was actually nice enough to give you an explanation as an answer instead. So I hope that "upvote" button gets some attention. – Neil Lunn Jun 21 '17 at 13:50
  • Of cause Neil - I always upvote you all I can :) and thanks for the links and the information. I learned a lot from your code, in fact I called the whole team to look at your elegant solution and we are now changing several other places because of your code :) – torbenrudgaard Jun 21 '17 at 17:10
  • Neil - sorry to disturb again. Everything works great and it is updating all records, but it is throwing a duplicate error, and I cant see where and what is causing it. I checked the array but there does not seem to be any null values in it. Here is the error: https://db.tt/Pffeg1UruT – torbenrudgaard Jun 21 '17 at 18:19
  • @torbenrudgaard I'm really just correcting the handling of looping async functions here. The one thing I do notice is that `substring(3)` seems incorrect, and should be `susbtring(0,3)` to read the first three chars from the string. If the string was less than 3 chars `null` would be what gets sent through the driver and explains the error. Anything beyond that is a problem with the data itself. If you have a new question then [Ask a New Question](https://stackoverflow.com/questions/ask) instead. It's much clearer to express each issue that way. The loop process itself is sound. – Neil Lunn Jun 21 '17 at 22:56
  • @torbenrudgaard Also please read the many `Promise` links I gave to you. You need to understand how promises work. All error handling should be done "outside" of this code, as it's job is solely to generate the array of promises and return it. – Neil Lunn Jun 21 '17 at 22:58