🚀

Reactでの非同期処理の書き方

に公開

結論 useEffect内での非同期処理はこう書く

useEffect(() => {
  const fetchData = async () => {
    try {
      const res = await fetch('https://example.com/api/users');
      const data = await res.json();
      setUsers(data);
    } catch (err) {
      console.error('エラー:', err);
    }
  };
  fetchData();
}, []);

useEffectの中でasync/awaitをつかって書くのが一般的

なぜ.then()ではなくasync/awaitを使うのか?

同じ処理は.then()でも書けます。

useEffect(() => {
  fetch('https://example.com/api/users')
    .then((res) => res.json())
    .then((data) => setUsers(data))
    .catch((err) => console.error('エラー:', err));
}, []);

この書き方も問題ありません。しかし、MDN公式ドキュメントには以下のように書かれています。

The purpose of async/await is to simplify the syntax necessary to consume promise-based APIs.

つまり、async/awaitはPromiseベースのAPIを扱うために、よりシンプルな構文を提供するためのものです。

また、多くの技術ブログでもasync/awaitの利用を推奨する理由として、
・ネストが浅くなり、読みやすくなる
・直感的で、同期処理のように見える
・エラーハンドリング(try/catch)がしやすい
などが挙げられています。

もちろん、.then()も簡潔な処理には有効で、「簡単な処理なら.then()、複雑な処理ならasync/await」という使い分けも一つの判断基準になると思います。

余談Promiseについて

「非同期処理の結果を表すオブジェクト」
今は結果がわからないけど、将来的に成功か失敗のどちらかになる」という約束(promise)を表します。

🧑‍🍳「ラーメン作るから5分待って!できたら教えるね!」

これがPromise。今はラーメン(=結果)はないけど、将来できあがることを約束している。

以下がPromiseオブジェクトの簡略的なイメージです。

{
  [[PromiseState]]: "pending" | "fulfilled" | "rejected",
  [[PromiseResult]]: 任意の値(成功時はデータ、失敗時はエラー)
}

↓具体的なPromiseを使ったコード

const promise = new Promise((resolve, reject) => {
  // 何か時間がかかる処理
  setTimeout(() => {
    const isSuccess = Math.random() > 0.5; // 成功・失敗をランダムに決定
    if (isSuccess) {
      resolve('成功しました');
    } else {
      reject('失敗しました');
    }
  }, 1000);
});

promise
  .then((res) => {
    console.log('成功:', res);
  })
  .catch((err) => {
    console.error('エラー:', err);
  });

new Promiseの第一引数には、成功(fulfilled)状態にしたいときに呼ぶ関数を、第二引数には失敗(rejected)状態にしたいときに呼ぶ関数を書きます。名前はなんでもいいですが、慣例的にresolveとrejectとします。
つまり...
・resolve(...) を呼ぶ → Promise が fulfilled 状態になる(.then() に進む)
・reject(...) を呼ぶ → Promise が rejected 状態になる(.catch() に進む)

promise
  .then((res) => {
    console.log('成功:', res);
  })

こちらのresには、resolve()に書いたもの、つまり今回の場合だと、1秒後に「成功しました」が入り、consoleには「結果: 成功しました」が表示されます。

まとめ

async/awaitPromiseをシンプルに扱うための構文
.then()よりも可読性が高く、積極的に使っていくのが妥当
useEffectasyncを使う際は、関数を定義して即時実行するのが正解

↓非同期処理の進化

出来事 説明
2015年 ES6(ES2015)で Promise 標準化 非同期処理の結果を扱うための新しいオブジェクト「Promise」が導入され、コールバック地獄(callback hell)を解消するための基盤となる。
2015年 .then() メソッドの導入 Promiseの成功・失敗時の処理をチェーン形式で記述できるメソッド .then() が同時に導入。これにより、非同期処理の流れをより直感的に書けるようになる。
2015年 fetch() API 登場(ブラウザAPI) XMLHttpRequest に代わる、Promiseベースの HTTP 通信 API。より簡潔でモダンな非同期通信が可能に。
2017年 async/await(ES2017)登場 Promiseをさらに扱いやすくするために、同期処理のように非同期処理を書ける async/await 構文が導入され、コードがより簡潔で読みやすくなる。

おわりに

async/await は、非同期処理をより簡潔に書けるようにするために、Promise(.then())の後に導入された構文です。実際、.then() よりもコードの可読性が高く、積極的に使っていくのが妥当だと感じました。

また、実際に使ってみると、Promiseの存在をあまり意識することなく非同期処理が書けるため、とても直感的で扱いやすいと感じます。これは、非同期処理に慣れていない人でもスムーズにコードを書けるという大きな利点でもあります。

一方で、Promiseの仕組みを知らなくても非同期処理が書けてしまうため、Promiseそのものの理解が後回しになりがちという一面もあるかもしれません。

今回は、Reactにおける非同期処理の書き方を調べる中で、これまで深く勉強してこなかったPromiseについても改めて調べ、理解を深めることができたので、その気づきを余談としてまとめておきました。

Discussion