0

I am learning to work with NodeJS and I came with a problem. getCurrentWeather() is asynchronous function which loads instantly when I start the app, and writes data to variables. When I use those variables outside the function to console.log() data, I get undefined results, because Node doesn't wait until getCurrentWeather() gets API data, it just executes code instantly while it still doesn't have anything to return. I solved it with another function renewCurrentWeather() and added setTimeout to wait while getCurrentWeather() gets data, and only then console.log() it.

This method works for me, but the problem is that if I want to use this data more than one time,I have to use the function wiht setTimeout. This looks to me a little buggy, because I need to use this data in more complex situations.

So my question is, how do I make Node.JS execute console.log(temp, cond) only then, when getCurrentWeather() fully finished loading data from API. In other words, I just want to use variables temp and cond everywhere in the app, without any funtions attached to it.

// Get weather data from Weather Underground every 3 minutes (due to daily 500 calls per day limit)

function getCurrentWeather() {
wunder.conditions('', function (err, data) {
 if (err) throw err;
 temp = data.temp_c;
 cond = data.weather;
});
};

getCurrentWeather();
setInterval(getCurrentWeather, 180000);

// Use data recieved from API as much times as I need
function renewCurrentWeather() {
  setTimeout(function() {
  console.log(temp + " " + cond);
}, 1000);
};
Ernestas
  • 25
  • 6
  • 2
    One of the most asked `javascript` questions on SO - [How to return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-asynchronous-call) – Adam Jenkins Jun 25 '15 at 11:42

3 Answers3

1

You can use the values by putting console.log in your callback function. Callback function works sequentially so console.log will executed after the the values have been updated.

function getCurrentWeather() {
    wunder.conditions('', function (err, data) {
     if (err) throw err;
     temp = data.temp_c;
     cond = data.weather;
    });
    console.log(temp + " " + cond);
};

setInterval(getCurrentWeather, 180000);
Pratyush Khare
  • 689
  • 5
  • 16
0

You can initiate the variables as false and then check their status before continuing with anything else.

// Get weather data from Weather Underground every 3 minutes (due to daily 500 calls per day limit)

// define variables as false
var temp = false;
var cond = false;

function getCurrentWeather() {
wunder.conditions('', function (err, data) {
 if (err) throw err;
 temp = data.temp_c;
 cond = data.weather;
});
};

getCurrentWeather();
setInterval(getCurrentWeather, 180000);

// Use data recieved from API as much times as I need
function renewCurrentWeather() {
  setTimeout(function() {
  // Check if the variables are set first and only then do whatever you want
  if(!temp && !cond){
      console.log(temp + " " + cond);
  }
}, 1000);
};

Alternatively, only proceed when the variables have been set in the callback

// Get weather data from Weather Underground every 3 minutes (due to daily 500 calls per day limit)

// define variables as false
var temp = false;
var cond = false;
var initiated = false;

function getCurrentWeather() {
wunder.conditions('', function (err, data) {
 if (err) throw err;
 temp = data.temp_c;
 cond = data.weather;
 // variables are now set so you can continue with rest of application
 if(!initiated){
     initiated = true;
     initiateApplication();
 }
});
};

getCurrentWeather();
setInterval(getCurrentWeather, 180000);
};
Rob Schmuecker
  • 8,934
  • 2
  • 18
  • 34
  • 2
    I think it's a bad idea to encourage people to use the `setTimeout` anti-pattern to deal with `asynchronous` programming. – Adam Jenkins Jun 25 '15 at 11:44
  • @Adam I very much agree. My answer was not intended to spark debate or restructure the entire flow of the OP's application, but rather to present *a* possible control-flow to allow the variables to only be used once defined. – Rob Schmuecker Jun 25 '15 at 11:49
  • @Adam In my opinion it does, the two examples above highlight how to prevent code execution until the callback has completed and assigned the variables `temp` and `cond` respectively. What else would you suggest? – Rob Schmuecker Jun 25 '15 at 11:56
0

If you're learning NodeJs, what you need to learn first is asynchronous code writing. You have multiple concepts to do do that, like async callbacks or promises.

Async callbacks are the most used in NodeJS. It's upon them that is built Express, its router, and its middleware system.

Because NodeJS is heavily based on async callbacks, there is a NodeJs convention which asks you to provide a callback function as last argument of an async function.

function callbackExample (err[, data][, callback]) {
    callback();
}

You could then rewrite your code like this:

function getCurrentWeather(callback) {
    wunder.conditions('', function (err, data) {
        if (err)
            throw err;

        // Save data
        temp = data.temp_c;
        cond = data.weather;

        // Run callback function if one is provided, and send it data
        if (callback)
            callback(null, { temp: temp, cond: cond });
    });
};

setInterval(getCurrentWeather, 180000);

You have a function that takes an optional callback function. When you want to get the last updated data, call getCurrentWeather with a callback function:

getCurrentWeather(function (err, data) {
    if (err)
        throw err;

    // Here you can do what you want with data
    console.log(data.temp);
    console.log(data.cond);
});

You should also read about the Javascript Event-Loop.

This is just an introduction to asynchronous writing, it does not fit exactly your needs because it'll update currentWeather each time you call getCurrentWeather, but it's a good way to start.

What you need, to execute the refresh weather only once each 3 minutes, is to have a queue of registered callback functions waiting for the weather to update.

kube
  • 13,176
  • 9
  • 34
  • 38