Nikhil
Nikhil

Reputation: 2318

Nodejs: How to avoid nested .then() when using async/await

Following is what I'm trying to do this in nodejs. The Rest API takes a city name as an input. I am trying to get the latitude and longitude using the geocode API for the input city. then, using the latitude and longitude, I am trying to get a list of closest cities using another API. then, for all those cities, I am getting the weather report, then for those cities, I am getting whether there is water and I am returning this back as a JSON.

As you can see, there is a lot of then and the goal of this exercise is to avoid nested callbacks.

I am using async/await which is supposed to have eliminated the nested then functions. But I don't see another way of doing this. The complete code snippet is below. The ugly part I am trying to fix is requester.makeRequest()

Following is just a snippet of the necessary code and not the complete working code. Any help on how to untangle this would be greatly appreciated.

app.get('/search', function(req, res, next) {
  const requester = {
      lastRequest: new Date(),
      makeRequest: async function(url) {
        const response = await fetch(url);
        const json = await response.json();
        return json;
      }
  };

requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1`
    + geocode_token)
  .then(function(city){
    var final_result = []
    var lat = city.latt;
    var long = city.longt;
    // request to get list of cities closer to that location,
    //takes latitude and longitude as parameters
    requester.makeRequest(metaweather_url + '?lattlong='
     + lat + ',' + long)
    .then(function(closer_cities) {
      var cities_len = closer_cities.length
      for(i = 0; i < closer_cities.length; i++) {
        woeid = closer_cities[i].woeid
        //request to get weather using woeid parameter
        requester.makeRequest(woeid_url + woeid)
        .then(function(weather) {
          var lattlong = weather.latt_long;
          requester.makeRequest(onwater_url+ lattlong +
          '?access_token=' + water_access_token)
          .then(function(onwater) {
            var temp = Object.assign(weather, onwater)
            final_result.push(temp)
            if (final_result.length == cities_len) {
              res.status(200).json({error: false,
                data: {message: final_result}})
            }
          })
        })
       }
      })
    })
  })

Upvotes: 2

Views: 1183

Answers (4)

MarcoS
MarcoS

Reputation: 17711

When calling async functions you are not supposed to use .then(...) construct...
Simply let result = await myAsynchronousFunction(a, b, c); ...

Upvotes: 1

Taki
Taki

Reputation: 17654

for this line : requester.makeRequest ... .then(function(city){

replace .then(function(city){ with var city = await requester.makeRequest , city will have the fulfilled value of the promise, do this for the rest of thens :

( keep in mind that await is only used inside an async function, you can use an iife )

(async () => {


  var city = await requester.makeRequest(`${geocode_url}?locate=${req.query.q}&json=1${geocode_token}`);

  var final_result = []
  var lat = city.latt;
  var long = city.longt;
  // request to get list of cities closer to that location,
  //takes latitude and longitude as parameters
  var closer_cities = await requester.makeRequest(`${metaweather_url}?lattlong=${lat},${long}`);

  var cities_len = closer_cities.length;

  for (i = 0; i < closer_cities.length; i++) {
    woeid = closer_cities[i].woeid
    //request to get weather using woeid parameter
    var weather = await requester.makeRequest(woeid_url + woeid);

    var lattlong = weather.latt_long;
    var onwater = await requester.makeRequest(`${onwater_url}${lattlong}?access_token=${water_access_token}`);

    var temp = Object.assign(weather, onwater)
    final_result.push(temp)
    if (final_result.length == cities_len) {
      res.status(200).json({
        error: false,
        data: {
          message: final_result
        }
      })
    }
  }

})();

Upvotes: 1

Estus Flask
Estus Flask

Reputation: 222493

then is misused in the first place because it results in callback hell. Promises are callback-based but they support chaining which is supposed to eliminate nested callbacks.

It should be:

  requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1` + geocode_token)
  .then(function(city){
    var final_result = []
    var lat = city.latt;
    var long = city.longt;

    return requester.makeRequest(metaweather_url + '?lattlong='
     + lat + ',' + long)
  })
  .then(function(closer_cities) {
     ...
  });

If there's a promise inside then, it should be returned. This way there's no more than a single level of callback nesting.

await is syntactic sugar for then, and rejections should be handled as well:

app.get('/search', function(req, res, next) {
  try {
    ...
    const city = await requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1`
      + geocode_token);
    var final_result = []
    var lat = city.latt;
    var long = city.longt;

    const closer_cities = await requester.makeRequest(metaweather_url + '?lattlong='
         + lat + ',' + long);
    ...
  } catch (err) {
    next(err)
  }
});

Upvotes: 1

qCazelles
qCazelles

Reputation: 53

I would say you still need one then

requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1`
    + geocode_token)
  .then(async function(city){
    var final_result = []
    var lat = city.latt;
    var long = city.longt;
    // request to get list of cities closer to that location,
    //takes latitude and longitude as parameters
    closer_cities = await requester.makeRequest(metaweather_url + '?lattlong='+ lat + ',' + long);
    var cities_len = closer_cities.length;
    for(i = 0; i < closer_cities.length; i++) {
      woeid = closer_cities[i].woeid
      //request to get weather using woeid parameter
      weather = await requester.makeRequest(woeid_url + woeid)
      var lattlong = weather.latt_long;
      onwater = await awaitrequester.makeRequest(onwater_url+ lattlong + '?access_token=' + water_access_token)
      var temp = Object.assign(weather, onwater)
      final_result.push(temp)
      if (final_result.length == cities_len) {
        res.status(200).json({error: false, data: {message: final_result}})
      }
    }
  })

Edit: I don't really think my answer is relevant for your problem sorry

Upvotes: 2

Related Questions