Sam
Sam

Reputation: 87

How to use async or promises Node/Express

I posted a previous question here, How to fix a race condition in Node.js/Express. Where my console will update correctly but my webpage doesn't update

Basically i want to know how to get my code to finish loading before my webpage updates. I have heard that promises or async work but i have not be able to employ them correctly in my code. I have made a simple version of my code below. Currently when i load my page my weather function is correctly updated but my flickr API takes two more page reloads before its results are displayed. Could someone please show me how to use Async or Promises to load all my data and update the page at once?

app.get('/', function (req, res) {
  // Render the webpage
  res.render('index', {weather: null, headlocation: null, lat: null, long: null, imgLinks: null, WebLinks: null, imgLinksFl: null, restLat: null, restLong: null, restname: null, error: null});
})

// Main Page
app.post('/', function (req, res) {
  city = req.body.city; // Grab the users input city 
  //console.log(weatherSort); // Debugging
  weatherSearch(); // Openweather API 
  filckrSearch(); // Flickr API

  res.render('index', {weather: weatherText, headlocation: headLocationText, lat: latLocation, long: longLocation, imgLinks: imageLinks, WebLinks: websiteLinks, imgLinksFl: imageLinksFlick, restLat: latitudeRest, restLong: longitudeRest, restname: restName, error: null});

});

// Weather function 
function weatherSearch(){

  // API URL
  let urlw = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKeyWeather}`

  // Send out a request 
  request(urlw, function (err, response, bodyW) {
    // Check for errors
    if(err || (JSON.parse(bodyW).cod == '404') || (JSON.parse(bodyW).cod == '401')){
      // If errors are found initialize all variables to empty so that it protects from future errors 
      // in other API functions 

    } else { 
      let weather = JSON.parse(bodyW) // Get JSON result

      weatherText = `It's ${weather.main.temp} degrees in ${weather.name}! ${weatherSort}: ${weatherInfo}`;
      headLocationText = `The City of ${basicLocation}`; 
    }
  });

}

// Flickr API
function filckrSearch(){

  // Create a new Flickr Client 
  var flickr = new Flickr(apiKeyFlickr);
  // Search Flickr based on latitude and longitude of city 
  flickr.photos.search({
    lat: latLocation,
    lon: longLocation,
    radius: 20, // Set radius to 20km 
    sort: flickrsort // Sort the photos by users selection 
  }).then(function (res) {
      var farmid = res.body.photos.photo[0].farm;
  }).catch(function (err) {
    console.error('bonk', err); // Catch errors 
  });
}

Upvotes: 0

Views: 208

Answers (3)

nfadili
nfadili

Reputation: 1342

Both your weatherSearch and flickrSearch functions are executing asynchronously but in different ways. weatherSearch is making a network request and then updating your text global variables in a callback. flickrSearch is also making a network request but is handling the response via the Promise API.

The issue with your express route code is that it is not written to handle the asynchronous code you are calling in weatherSearch and flickrSearch. The simplest way to fix this is to remove the global variables you are updating in your functions, and have them return the values they retrieve with their network requests. Here is a quick example:

// Main Page
app.post('/', async function (req, res) {

  const weatherResults = await weatherSearch(); // Here we 'await' the response before rendering the HTML

  res.render('index', {
    weather: weatherResults.weatherText, 
    headlocation: weatherResults.headLocationText
  });

});

// Weather function 
async function weatherSearch() {
  let urlw = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKeyWeather}`
  return new Promise(function (resolve, reject) {
    request(url, function (error, res, body) {
      if (err || (JSON.parse(bodyW).cod == '404') || (JSON.parse(bodyW).cod == '401')){
        // This is how the Promise API 'returns' an error on failure
        reject(); 
      }
      else { 
        let weather = JSON.parse(bodyW)
        // This is how the Promise API 'returns' a value on success
        resolve({
          weatherText: `It's ${weather.main.temp} degrees in ${weather.name}! ${weatherSort}: ${weatherInfo}`,
          headLocationText: `The City of ${basicLocation}`
        })
      }
    });
  });
}

Understanding async code in node is extremely important! There a lot of great articles on Promises, async await, and callbacks out there that you can use to become familiar with it.

Upvotes: 1

nosleepfilipe
nosleepfilipe

Reputation: 76

Give it a try to BlueBird is really easy to use and you will find a lot of examples on the documentation .

Upvotes: 0

David784
David784

Reputation: 7464

Here's a partial example of how you might "promisify" weatherSearch. Same basic idea for the other one...it would be redundant to include both of them.

// Main Page
app.post('/', async function (req, res) {
  city = req.body.city; // Grab the users input city 
  //console.log(weatherSort); // Debugging
  try {
    let { weatherText, headLocationText } = await weatherSearch(); // Openweather API 
    await filckrSearch(); // <- promisify the same as above

    res.render('index', { weather: weatherText, headlocation: headLocationText, lat: latLocation, long: longLocation, imgLinks: imageLinks, WebLinks: websiteLinks, imgLinksFl: imageLinksFlick, restLat: latitudeRest, restLong: longitudeRest, restname: restName, error: null });
  } catch (e) {
    // do something if you get an error
  }
});

// Weather function 
function weatherSearch() {

  // API URL
  let urlw = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKeyWeather}`

  // Send out a request 
  return new Promise((resolve, reject) => {
    request(urlw, function (err, response, bodyW) {
      // Check for errors
      if (err || (JSON.parse(bodyW).cod == '404') || (JSON.parse(bodyW).cod == '401')) {
        // If errors are found initialize all variables to empty so that it protects from future errors 
        // in other API functions 
        reject(err);
      } else {
        let weather = JSON.parse(bodyW) // Get JSON result

        weatherText = `It's ${weather.main.temp} degrees in ${weather.name}! ${weatherSort}: ${weatherInfo}`;
        headLocationText = `The City of ${basicLocation}`;
        resolve({ weather, weatherText, headLocationText });
      }
    });
  });
}

Basic premise is:

  • you wrap the function with callback in a Promise, and call the resolve/reject functions in the appropriate places.
  • make sure you return the Promise
  • when calling, you can use the async/await as above, or you can use .then() and .catch().
  • Either way, I think it's considered a better practice to return the values as shown, rather than using global variables or variables in a wrapping closure.
  • you probably want to catch any errors in some fashion. That way if a problem happens it won't crash your web server, and you can also display or log the error as appropriate, and send the user whatever error page you want. My example shows the try/catch, which is generally used with async/await.

Upvotes: 1

Related Questions