How do I fix a situation in which findOne() runs after it should, causing the function it is in to return 'undefined'?

Tell us what’s happening:
As you can see in this image, the fetch_stockdata() function returns ‘undefined’.

The reason it does this seems to be because the fetch_stockdata() function returns the value BEFORE the findOne() method and the code inside it is complete.

So the order I believe the program is running the code is:

  1. Run fetch_stockdata()
  2. Within fetch_stockdata() run fetch_price()
  3. return stockdata_obj (which is undefined at the moment)
  4. Within fetch_stockdata() run db_model.findOne() method

What I want the program to do instead is:

  1. Run fetch_stockdata()
  2. Within fetch_stockdata() run fetch_price()
  3. Within fetch_stockdata() run db_model.findOne() method
  4. return stockdata_obj (which is now not undefined as it has been filled by the db_model.findOne() method)

How can I fix this?

Your project link(s)

solution: https://replit.com/@jaimeggb/boilerplate-project-stockchecker-4

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36

Challenge: Stock Price Checker

Link to the challenge:

If fetch_stockdata returns a promise then you can use an async function and await it.

If it does not return a promise then make it into one:

let stockdata_obj = new Promise(res => res(jfunc.fetch_stockdata(
        req.query.stock,
        req.query.like,
        req.ip,
        Stock
    )))

stockdata_obj.then(data => console.log(`stock data: ${data}`));

You will need to use res.json in a subsequent .then callback if you want it to chronologically happen afterwards

2 Likes

Hi @caryaharper

I tried your code and got practically the same result as before:

Have I implemented the code wrong or what could be going on?

Thanks for your time

can you show me the definition for fetch_stockdata

1 Like

I have pasted my ‘Jfuncs.js’ file below, which includes ‘fetch_stockdata()’. You can find everything put together in this link:
solution: https://replit.com/@jaimeggb/boilerplate-project-stockchecker-4

const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;

class Jfuncs {

  fetch_price(stock_symbol) {
    let xhr = new XMLHttpRequest();
    let price;
    let requestURL = 'https://stock-price-checker-proxy.freecodecamp.rocks/v1/stock/' + stock_symbol + '/quote';
    xhr.open('GET', requestURL, false);
    xhr.onload = function () {
      price = JSON.parse(xhr.responseText).latestPrice;
    };
    xhr.send();
    return price
  }



  fetch_stockdata(stock_symbol, like_box_checked, user_ip, db_model) {
    
    // Fetch price
    let price = this.fetch_price(stock_symbol);
    console.log('price', price);
    
    // Fetch likes and return price and likes in variable object 'stockdata_obj'
    db_model.findOne({ stock_symbol: stock_symbol }, function(err, stockFound_1) {
      console.log('Like box checked? ', like_box_checked)

      if(err){
        return console.log(err);
      };

      switch(true) {
        case ((stockFound_1 != null) && (like_box_checked == 'true') && (stockFound_1.ips_that_like_it.includes(user_ip) == true)): {
          // 1 Return stockdata as is
          console.log('case 1');
          let likes_for_that_stock = stockFound_1.ips_that_like_it.length
          let stockdata_obj = {"stockData":{"stock":stock_symbol,"price":price,"likes":likes_for_that_stock}};
          return stockdata_obj;
          break;
        }
        case ((stockFound_1 != null) && (like_box_checked =='true') && (stockFound_1.ips_that_like_it.includes(user_ip) != true)): {
          // 2 Update stock document/record to append new IP and increase likes +1
          console.log('case 2');
          Stock.findByIdAndUpdate(
            stockFound_1._id, 
            { $push: {ips_that_like_it: user_ip} }, 
            {new: true}, 
            //callback (show response object here)
            function(err, stockUpdated_1) {
              if(err){
                return console.error(err);
              }else{
                let likes_for_that_stock = stockUpdated_1.ips_that_like_it.length
                let stockdata_obj = {"stockData":{"stock":stock_symbol,"price":price,"likes":likes_for_that_stock}};
                return stockdata_obj;
              };
            }
          ); 
          break;
        }
        case ((stockFound_1 != null) && (like_box_checked != 'true') && (stockFound_1.ips_that_like_it.includes(user_ip) == true)): {
          // 3 Return stockdata as is
          console.log('case 3');
          let likes_for_that_stock = stockFound_1.ips_that_like_it.length
          let stockdata_obj = {"stockData":{"stock":stock_symbol,"price":price,"likes":likes_for_that_stock}};
          console.log('this has run');
          console.log(stockdata_obj);
          return stockdata_obj;
          break;
        }
        case ((stockFound_1 != null) && (like_box_checked != 'true') && (stockFound_1.ips_that_like_it.includes(user_ip) != true)): {
          // 4 Return stockdata as is
          console.log('case 4');
          let likes_for_that_stock = stockFound_1.ips_that_like_it.length
          let stockdata_obj = {"stockData":{"stock":stock_symbol,"price":price,"likes":likes_for_that_stock}};
          return stockdata_obj;
          break;
        }
        case ((stockFound_1 == null) && (like_box_checked == 'true') /* && stockFound_1.ips_that_like_it.includes(user_ip) != true */): {
          // 5 Create new stock document/record in DB and return stock data
          console.log('case 5');
          const createAndSaveStock = function(done) {
            new Stock({
              stock_symbol: stock_symbol,
              ips_that_like_it: [user_ip],
              likes_for_that_stock: 1
            }).save(function(err, stock_created_1) {
              console.log(stock_created_1);
              if(err){
                return console.error(err);
              }else{
                //Show client the stock_symbol and id pair created and saved
                let stockdata_obj = {"stockData":{"stock":stock_symbol,"price":price,"likes":likes_for_that_stock}};
                return stockdata_obj;
                done(null,stock_created_1);
              };
            });
          };
          createAndSaveStock(() => {});
          break;
        }
        case ((stockFound_1 == null) && (like_box_checked != 'true') /* && stockFound_1.ips_that_like_it.includes(user_ip) != true */): {
          // 6 Return stockdata as is
          console.log('case 6', like_box_checked);
          let stockdata_obj = {"stockData":{"stock":stock_symbol,"price":price,"likes":likes_for_that_stock}};
          return stockdata_obj;
          break;
        }

      }; // closes 'switch (true) '

    }); // closes 'db_model.findOne({ stock_symbol: stock_symbol }, function(err, stockFound_1) '

  } // closes 'fetch_stockdata(stock_symbol, like_box_checked, user_ip, db_model) '



}

module.exports = Jfuncs;

I have found a clean solution others may benefit from:

'use strict';

//Install and set up mongodb and mongoose
const mongodb = require('mongodb');
const mongoose = require('mongoose');
const mySecret = process.env['MONGO_URI']

const Jfuncs = require('../controllers/Jfuncs.js');


module.exports = function (app) {

  // This variable creates an instance of the class Jfuncs (i.e. creates an object) when called
  let jfunc = new Jfuncs();

  //Connect mongoose to mongodb database
  mongoose.connect(mySecret, { useNewUrlParser: true, useUnifiedTopology: true });
  //Create a schema and a Model for data going into mongodb database 
  const { Schema } = mongoose;
  const stockSchema = new Schema({
      stock_symbol: {type: String, required: true},
      ips_that_like_it: {type: [String], required: true},
      likes_for_that_stock: {type: Number, required: true}
    });
  const Stock = mongoose.model('Stock', stockSchema);

  // What must happen when we click the first 'Get Price!' button
  app.route('/api/stock-prices')
    .get(async function (req, res){
      if (typeof req.query.stock == 'string') {
        // 1 stock - Get single price and total likes
        let stockFound_1 = await Stock.findOne({ stock_symbol: req.query.stock }).exec();
        console.log(stockFound_1);
        let stockdata_obj = jfunc.fetch_stockdata(
          req.query.stock, 
          req.query.like, 
          req.ip, 
          stockFound_1
        );
        return res.json(stockdata_obj);

      } else if(typeof req.query.stock == 'object' /* In JS, an array is type object*/) { 
        // 2 stocks - Compare and get relative likes

        // Get first stock object
        let stockFound_1 = await Stock.findOne({ stock_symbol: req.query.stock[0] }).exec();
        let stockdata_obj_1 = jfunc.fetch_stockdata(
          req.query.stock[0], 
          req.query.like, 
          req.ip, 
          stockFound_1
        );
        console.log(stockdata_obj_1);

        // Get second stock object
        let stockFound_2 = await Stock.findOne({ stock_symbol: req.query.stock[1] }).exec();
        let stockdata_obj_2 = jfunc.fetch_stockdata(
          req.query.stock[1], 
          req.query.like, 
          req.ip, 
          stockFound_2
        );
        console.log(stockdata_obj_2);

        // Join the two in a single object to show user relative likes
        let stockdata_objs_compared = jfunc.stockdata_comparison_obj_creator(stockdata_obj_1,stockdata_obj_2);
        return res.json(stockdata_objs_compared);

      }; // closes 'else if'

    }); // closes '.get route'

}; // closes 'module.exports'

The keys to the solution are: using ‘async’ and ‘await’, and retrieving records from mongoDB using the following simple line of code, instead of complicated callback functions:

let stockFound_1 = await Stock.findOne({ stock_symbol: req.query.stock }).exec();