LoginSignup

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

関数内の関数/ 同期通信、非同期通信

解決したいこと

関数内の関数/同期通信、非同期通信

JavaScriptで天気予報のアプリを作っています。関数内の関数の変数をスコープによって取り出し、それを使って現地時刻に合わせた曜日を表示したいです。(例えば東京を検索し、今日が月曜日の場合下の週間天気予報には火、水、木。。。のように)

showForecast関数内でshowWeekdaysForecast関数の変数を使ったinnerHTMLの記述をするところが問題でしょうか?(途中までshowForecast関数のresponseからshowWeekdaysForecast関数に受け渡し、その後またshowForecast関数内で処理をするということになるため)

発生している問題・エラー

Uncaught (in promise) TypeError: Cannot read property 'data' of undefined
    at showWeekdaysForecast (index copy.js:156)
    at showForecast (index copy.js:161)

該当するソースコード

//show forecast
function showForecast(response) {
  console.log(response); //weekly forecast
  //change icons
  let forecastElements = document.getElementById("forecast");
  forecastElements.innerHTML = null;

  let forecast = null;

  //Temperature
  let maxTemp = null;
  let minTemp = null;

  //Local time
  let localTimeApiKey = "KTPS14XG8OUY";
  let latitude = response.data.lat;
  console.log(latitude);
  let longitude = response.data.lon;
  let localTimeApiUrl = `https://api.timezonedb.com/v2.1/get-time-zone?key=${localTimeApiKey}&format=json&by=position&lat=${latitude}&lng=${longitude}`;
  axios.get(localTimeApiUrl).then(showWeekdaysForecast);

  //show the day of a week in local time
  let wDayNum = 0;
  function showWeekdaysForecast(weekDays) {
    //console.log(weekDays); //local data 現地情報(オブジェクト) →「undefined」と出ます。
    let wDay = new Date(weekDays.data.formatted);
  //↑ここで「Cannot read property 'data' of undefined」と出ます

    //console.log(wDay); 現地の日付、時刻
    wDayNum = wDay.getDay();
    console.log(wDayNum); //weekday number
  }
  showWeekdaysForecast();
 //↑こちらも「dataが定義されていない」と出ます。

  console.log(wDayNum);

  let weekdays = [
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
  ];
  let weekDay = document.getElementById("weekDay");
  weekDay.innerHTML = null;

  for (let index = 1; index < 7; index++) {
    forecast = response.data.daily[index];
    maxTemp = Math.round(response.data.daily[index].temp.max);
    minTemp = Math.round(response.data.daily[index].temp.min);

  //↓ここでwDayNumが必要です。
    forecastElements.innerHTML += `<div class="circle col-md-1">
    <div class=col-md-1" id="weekDay">${weekdays[wDayNum + index]}</div >
    <img id="imgForecast" src="https://openweathermap.org/img/wn/${
      forecast.weather[0].icon
    }@2x.png"/>
    <h3>${maxTemp}℃/${minTemp}℃</h3>
    </div>`;
  }
}

自分で試したこと

async, awaitをfunction showForecastで使いましたがうまくいきませんでした。

0

2Answer

まず、 Cannot read property 'data' of undefined エラーが出るのは showWeekdaysForecast() をすぐに呼んでいるせいです。

  function showWeekdaysForecast(weekDays) {
    //console.log(weekDays); //local data 現地情報(オブジェクト) →「undefined」と出ます。
    // ↑ここで weekDays が undefined なのは……
    
  }

  // ↓ここで引数を渡さずに呼んでいるから
  showWeekdaysForecast();

次に wDayNum に関する話です。関数の外側の変数を参照してその値を書き換えることは、単純な例ならば以下のように実現できます:

function main() {
  let value = 0;

  function setValue() {
    value = 42;
  }
  setValue();

  console.log(value); // => 42
}

しかし axios によるリクエストのような、 Promise が絡んでくるコードでは話が別です。 Promise は非同期に実行されるので、 .then() で登録した関数はいつ呼ばれるか分からないからです(少なくとも現在のイベントループ完了前には呼ばれない、つまり同期的には呼ばれないことが仕様で保証されています)。

function main() {
  let value = 0;

  function setValue() {
    value = 42;
  }

  // ここで axios.get('/xxx') は Promise オブジェクトを返す。
  // Promise に対して .then() を呼んで、 Promise 成功時に実行する関数を登録する。
  axios.get('/xxx').then(setValue);

  // まだ setValue() は呼ばれていない。
  console.log(value); // => 0

  // setValue() が呼ばれるタイミングは必ずこれ以降になる。いつかは分からない。
}

.then() で登録した関数に続けて何かを実行したい場合には、 .then() をチェーンして書くことができます。このとき、先に登録した関数の返り値が次の関数の引数として渡されます。

function main() {
  function getValue() {
    return 42;
  }

  function showValue(value) {
    console.log(value);
  }

   axios.get('/xxx').then(getValue).then(showValue);

  // この後のいつかのタイミングでまず getValue() が呼ばれ、
  // その返り値を引数にして(さらに後のタイミングで) showValue() が呼ばれる。
}

よって、 showWeekdaysForecast で得た wDayNum を別の処理に渡したいなら、 wDayNum を返り値にし、それを引数で受け取る後続の処理を .then() で登録してください。

4

Comments

  1. なるほど!回答ありがとうございます。変数を求める関数とHTMLに書き込む関数を別にし、.then()でつなげたところエラーなく動きました。ありがとうございました!!

Your answer might help someone💌