0

I am trying to return a random value from JSON file after reading in and parsing. I get an undefined error when I try using the return but can console out the value. Is there a nice way of handling this?

const fs = require('fs');

function getRandFact() { 
    fs.readFile('facts.json', (err, data) => {
        if (err) throw err;
        const obj = JSON.parse(data);
        const keys = Object.keys(obj);
        const randIndex = Math.floor(Math.random() * keys.length);
        const randKey = keys[randIndex];
        const randVal = obj[randKey];
        console.log(randVal);
        return randVal;
    });
}

console.log(getRandFact());
  • Possible duplicate of [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – Patrick Roberts Dec 15 '18 at 05:59

2 Answers2

1

The code you are dealing with is running asynchronously. The logging happens after getRandFact() returned, when it actually received the value. The returning of a value inside an async callback does not help - it returns it in a different scope. Same goes for the error: If there is an error, it will throw it where it won't be handled.

You have at least two options in such a situation:

  1. Provide a callback to getRandFact() in order to use it

    function getRandFact(doSomethingWithRandFact) { 
        fs.readFile('facts.json', (err, data) => {
            if (err) return doSomethingWithRandFact(err);
            // ... all your  other stuff
            console.log(randVal);
            return doSomethingWithRandFact(null, randVal);
        });
    }
    
    getRandFact((err, fact) => {
      if (err) {
        return console.error("Something happened while reading facts.json:", err);
      }
      console.log(fact);
    });
    
  2. Turn getRandFact() into a function that returns a Promise and use that:

    function getRandFact() { 
      return new Promise((resolve, reject) => {
        fs.readFile('facts.json', (err, data) => {
          if (err) return reject(err);
          // ... all your other stuff
          console.log(randVal);
          return resolve(randVal);
        });
      });
    }
    
    getRandFact()
      .then(fact => console.log(fact));
      .catch(err => console.error("Something happened getting the random fact:", err));
    

I'd suggest option 2, as passing callbacks can lead to "callback hell" quite quickly and error handling is a bit trickier.

Narigo
  • 2,979
  • 3
  • 20
  • 31
0

You're confusing asynchronous code with synchronous code.

You'll have to understand how async code works: What is the difference between synchronous and asynchronous programming (in node.js)

Callbacks: Create a custom callback in JavaScript

Finally, after you've learnt a bit about async and using callbacks - here's an explanation.

const fs = require('fs');

function getRandFact() { 
    fs.readFile('facts.json', (err, data) => {
        if (err) throw err;
        const obj = JSON.parse(data);
        const keys = Object.keys(obj);
        const randIndex = Math.floor(Math.random() * keys.length);
        const randKey = keys[randIndex];
        const randVal = obj[randKey];
        console.log(randVal);
        return randVal;
    });
}

console.log(getRandFact());

As soon as you call getRandFact(), the fs.readFile function is called and this internally starts to process the file (basically you can say it's reading and this reading is async so it takes time) but the getRandFact the functions returns undefined. So, your console log prints undefined. Then at some later point of time, the reading by the fs finishes and then it calls the callback function viz. (err, data) => {} with the arguments so that they can now be used.

So, to solve this problem you'll have to do something like this:

const fs = require('fs');

function getRandFact(callback) { 
    fs.readFile('facts.json', (err, data) => {
        if (err) throw err;
        const obj = JSON.parse(data);
        const keys = Object.keys(obj);
        const randIndex = Math.floor(Math.random() * keys.length);
        const randKey = keys[randIndex];
        const randVal = obj[randKey];
        console.log(randVal);
        callback(randVal); // Notice that we called the callback function
        // function and passed the random value as the parameter to it
    });
}

// This will be called with the randomValue that 
// will be generated and then you can use that value
function callback (randomValue) {
  console.log(randomValue);
}

// We're passing the callback function
// as an argument to the getRandFact function
getRandFact(callback);
Sivcan Singh
  • 1,775
  • 11
  • 15