async/awaitについて今一度整理する

# きっかけ
久しぶりにreactを使って[webアプリケーションを開発した](https://qiita.com/koichi2426/items/4b4eb65fd85f4c6edaea)のだが、async/awaitの使い方を忘れていた。なので、もう一度学び直したい。

# async/awaitとは

`async/await` は、JavaScript の非同期処理をシンプルに書くための構文。非同期処理を同期的な見た目で記述できるため、コードの可読性が高まる。

* `async` を付けた関数は自動的に Promise を返す。
* `await` を使うと、Promise が解決されるまで処理を一時停止できる。ただし `await` は `async` 関数の中でしか使えない。

# 本番のコードを使って解説

以下のコードは、現在地と天気情報を取得し、それをもとに詩を生成する React アプリの一部。非同期処理に `async/await` を多用しているため、順を追って解説する。

### 1. `fetchPoem` 関数の定義

```ts
const fetchPoem = async () => { ... }
```

ここで `async` を付けているため、関数内で `await` を使えるようになる。

### 2. 現在地を取得

```ts
const position = await new Promise<GeolocationPosition>((resolve, reject) => {
  navigator.geolocation.getCurrentPosition(resolve, reject);
});
```

`getCurrentPosition` は[コールバック形式](https://qiita.com/nakajima417/items/4d0c2d46ff82351549e6)なので、`Promise` にラップして `await` で扱えるようにしている。

### 3. 天気APIの呼び出し

```ts
const weatherRes = await fetch(`https://api.openweathermap.org/...`);
const weatherData = await weatherRes.json();
```

`fetch` は非同期関数。レスポンスが返るまで待ち、次に `response.json()` を `await` で待ってからデータを取得する。

### 4. 詩の生成APIの呼び出し

```ts
const res = await fetch("/api/GeneratePoem", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(environment),
});
const data = await res.json();
```

ここでも `await` を使って、API呼び出しからレスポンスの取得、JSON 変換までを順番に処理している。

### 5. try-catch-finally でのエラーハンドリング

```ts
try {
  ...
} catch (error) {
  console.error(error);
  setPoem("詩の取得に失敗しました");
} finally {
  setIsLoading(false);
}
```

非同期処理のどこかでエラーが起きても、`catch` でエラーメッセージを出力し、最後にローディング状態を解除するようにしている。

### 6. 実行順番

1. **コンポーネント `Home` の初回レンダリング**
2. **`useEffect` により `fetchPoem()` が一度だけ呼ばれる**
3. **`fetchPoem()` 内で以下の非同期処理が順番に実行される：**

   1. ブラウザから位置情報を取得（`navigator.geolocation.getCurrentPosition`）
   2. OpenWeather API で天気情報を取得（`fetch`）
   3. `weatherRes.json()` でレスポンスをパース
   4. 環境情報を整形
   5. 自作 API `/api/GeneratePoem` に POST リクエスト
   6. 詩のデータを取得し、`setPoem()` で更新
4. **finally 節で `setIsLoading(false)` を実行**
5. **`poem` または `isLoading` の state 変更により再レンダリング**


# コード全文
```typescript
'use client'; // このファイルはクライアントコンポーネントとして扱う

import { useEffect, useState } from "react";
import Header from "./components/layouts/Header/Header";
import Footer from "./components/layouts/Footer/Footer";
import CoccoCharacter from "./features/home/components/CoccoCharacter/CoccoCharacter";
import PoemBubble from "./features/home/components/PoemBubble/PoemBubble";

// Home コンポーネントの定義
export default function Home() {
  // 詩の内容を保持する state
  const [poem, setPoem] = useState("");
  // 読み込み中かどうかを示す state
  const [isLoading, setIsLoading] = useState(true);

  // コンポーネントの初回マウント時に実行
  useEffect(() => {
    // 詩を取得する非同期関数
    const fetchPoem = async () => {
      try {
        // 現在の位置情報を取得（Promise にラップして await を使用可能に）
        const position = await new Promise<GeolocationPosition>((resolve, reject) => {
          navigator.geolocation.getCurrentPosition(resolve, reject);
        });

        // 緯度・経度の取得
        const lat = position.coords.latitude;
        const lon = position.coords.longitude;

        // OpenWeather API を使って現在地の天気情報を取得
        const weatherRes = await fetch(
          `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${process.env.NEXT_PUBLIC_WEATHER_API_KEY}&units=metric&lang=ja`
        );

        // 天気情報を JSON に変換
        const weatherData = await weatherRes.json();

        // 詩の生成に必要な環境データを整形
        const environment = {
          location: weatherData.name, // 地名
          temperature: weatherData.main.temp, // 気温
          humidity: weatherData.main.humidity, // 湿度
          weather: weatherData.weather[0].description, // 天気の説明（例：くもり、晴れなど）
          time: new Date().toLocaleString("sv-SE", { timeZone: "Asia/Tokyo" }).replace(" ", "T") + ":00.000+09:00", // ISO8601形式に近い日時
        };

        // サーバーサイドのAPIに POST リクエストを送って詩を生成
        const res = await fetch("/api/GeneratePoem", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(environment), // 環境情報を送信
        });

        // レスポンスを JSON として受け取り、詩のテキストを抽出
        const data = await res.json();
        setPoem(data.text || "詩の取得に失敗しました"); // テキストがなければエラーメッセージを表示
      } catch (error) {
        // 位置情報取得やAPI呼び出しでエラーが発生した場合
        console.error(error);
        setPoem("詩の取得に失敗しました");
      } finally {
        // 読み込み状態を終了
        setIsLoading(false);
      }
    };

    // 詩の取得関数を実行
    fetchPoem();
  }, []); // 空配列のため、初回マウント時のみ実行

  // JSXの返却（UIレンダリング）
  return (
    <>
      <Header /> {/* ヘッダーコンポーネント */}
      <main className="min-h-screen bg-[#40494F] text-white flex flex-col">
        <div className="flex-grow flex items-start justify-center pt-16 sm:pt-32">
          <div className="flex flex-col items-center gap-4 sm:gap-6">
            {/* 詩の吹き出しコンポーネント。読み込み中は「考え中...」と表示 */}
            <PoemBubble poem={isLoading ? "考え中..." : poem} />
            {/* キャラクターのアニメーションなどに使用。読み込み状態を渡す */}
            <CoccoCharacter isLoading={isLoading} />
          </div>
        </div>
      </main>
      <Footer /> {/* フッターコンポーネント */}
    </>
  );
}

```