0

The situation is: I have an array of cities, function getCityForecast that fetches forecast for each of them and returns the received converted data into a needed format. I need to save them into the array and save this array in state of the component.

this.state = {
      cities: [
        'Санкт-Петербург',
        'Москва',
        'Казань',
        'Самара',
        'Красноярск',
        'Чита',
        'Челябинск',
        'Оренбург',
        'Ростов-на-Дону',
        'Орск'
      ],

      cityObjects: []
    };

    this.getCityForecast = this.getCityForecast.bind(this);
    this.renderCities = this.renderCities.bind(this);
}

getForecastData = async (cityKey, cityName) => {
    const apiKey = 'ExAmPlEkEy';
    const forecastUri = 'http://dataservice.accuweather.com/forecasts/v1/daily/1day/';
    const uriToFetch = `${forecastUri}${cityKey}?language=ru&metric=true&details=true&apikey=${apiKey}`;
    try {
      let response = await fetch(uriToFetch);
      if(response.ok) {
        let jsonResponse = await response.json(); // Converts into JSON
        if (jsonResponse.DailyForecasts) {
          let cityData = jsonResponse.DailyForecasts.map(forecast => ({ //Data converted here into a convenient object
            icon: forecast.Day.Icon,
            iconPhrase: forecast.Day.IconPhrase,
            tempValue: forecast.Temperature.Maximum.Value,
            tempUnit: forecast.Temperature.Maximum.Unit,
            cityKey: cityKey,
            cityName: cityName
          })   
        );

        let renderCity = cityData.map(city => ( // Presented in React.js manner
              <div
                className="weather"
                key={city.cityKey}>
                <h1>{city.cityName}</h1>
                <img
                  src={`http://apidev.accuweather.com/developers/Media/Default/WeatherIcons/${city.icon}-s.png`}
                  alt={city.iconPhrase}
                  className="weathericon" />
                <h2>{`Температура: ${city.tempValue}°${city.tempUnit}`}</h2>
              </div>
            )
          );

          return renderCity; // Retuns a formatted city forecast
        } else {
          return [];
        }
      }
      throw new Error('Forecast request failed!');
    } catch (error) {
      console.log(error);
    }
  }

renderCities = () => { // applies this.getCityForecast() to the array of city names
    if(this.state.cities) {
      const cityObj = Promise.all(this.state.cities
        .map(city => this.getCityForecast(city)))
          .then(promiseResp => (promiseResp)) // CASE ONE
          /*.then(val => (val))*/ // CASE TWO <-- Not the above promise's value
          .catch(e => {console.log(e)});
      console.log(cityObj); // Save response to this.cityObjects
    }
  }

So the issue is that in CASE ONE it returns:

_proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: Array(2) // <-- I need this content
  0: [{…}]
  1:  [{…}]
  length: 2
  __proto__:  Array(0)

And in CASE TWO I've got:

__proto__: Promise
[[PromiseStatus]]: "pending"
[[PromiseValue]]: undefined

How do I get CASE ONE's [[PromiseValue]] content?

  • What do you mean by "*if `then` doesn't work out*"? – Bergi May 17 '18 at 18:11
  • 1
    The situation you're in is a bit unclear. I think it won't hurt to refactor the code a little bit. First, let's make `getForecastData` a function, that actually returns fetched data and nothing else, and move react/jsx code from it to a new function maybe called `renderCities`. Then I suppose you could store city data in the state, and render it accordingly with `renderCities`? – Anton Harniakou May 17 '18 at 18:15
  • 1
    All `.then()` handlers are executed in cascade, they **are not** similar to the cases of a `switch` statement. The value returned by one `.then()` handler is the argument of the next `.then()` handler. `.then(promiseResp => (promiseResp))` is a no-op. It just returns the value it receives. – axiac May 17 '18 at 18:38

1 Answers1

1

You should make renderCities an async function and await the promise:

renderCities = async () => { // applies this.getCityForecast() to the array of city names
    if(this.state.cities) {
      const cityObj = await Promise.all(this.state.cities
        .map(city => this.getCityForecast(city)))
          .catch(e => {console.log(e)});
      console.log(cityObj); // Save response to this.cityObjects
    }
  }

.then(promiseResp => (promiseResp)) or .then(val => (val)) doesn't really do anything – you're just mapping the value inside the promise to itself.

See also How to access the value of a promise?

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
  • Super noob `await`/`async` question... do you need a `return` in here somewhere? Or is that handled automatically? – apsillers May 17 '18 at 18:12
  • 1
    @apsillers `renderCities` creates a promise for nothing (`undefined`), it doesn't return anything. The arrow function passed to `map` has its usual implicit return. – Bergi May 17 '18 at 18:13