川獺の外部記憶

なんでも残しておく闇鍋みたいな備忘録

非同期処理考察 番外編 axiosの使い方

非同期処理考察の本体に疲れたので使えればいいやぐらいで適当に実装したものの記録です。

もしかしたらすごくリスキーなコードになってるかもしれません。本体の非同期処理考察をやりきってから答え合わせします。

本エントリは以下の実装に対する解説形式で残します。

gitlab.com

axiosを使ってAPIを叩いている部分は以下の実装になっています。

axios
  .get(API_BASEURL + "books/") // axiosのget処理を開始する(非同期処理の開始を予約する)
  .then((response) => { // axiosのget処理がうまくいったときの処理。 responseに返ってきたjsonが全て入ってる。
    for (const item of response.data) {
      // APIから取得できた本のリストにの各要素「item」について、このコンポーネント内で扱える形(構造体)に変換する。
      // 変換したあとでsetBooks()をつかって「books」stateに入れていく。
      // 表示する「ステータス」の値をAPIから返ってきた「last_activity」を使って動的生成する。
      // 邪魔なら一旦見なかったことにしたほうが良い。
      var statusValue: string;
      if (item.last_activity && item.last_activity.type === "borrow") {
        statusValue = "持出し中" + " - " + item.last_activity.user.employee_id
      }
      else {
        statusValue = "利用可"
      }
      // 「ステータス」の動的生成ここまで。
      // 「newBook」を作る。これは取得できた1つのitemを構造体に変換したもの。
      const newBook = { id: item.id, title: item.title, note: item.note, status: statusValue }
      // 変換した構造体を「books」に追加する。
      setBooks((oldBooks) => [...oldBooks, newBook]) // [...oldBooks, newBook] は「oldBooks全てとnewBookからなる新しい配列」の表現。
    }
  })
  .catch((error) => { setHasError(true); }) // axiosのgetでエラーが起きたときの処理
  .finally(() => {
    setIsLoaded(true);
  }) // エラーが起きたかどうかに関わらず、axiosのgetが終わったあとの処理

上記はかなり省略した書き方で、省略をせずに書くと以下のような感じになるはずです。

const promiseForApiCall = axios.get(API_BASEURL + "books/") // axiosのget処理を開始する(非同期処理の開始を予約する)
const promiseForAfterApiCall = promiseForApiCall.then((response) => { // axiosのget処理がうまくいったときの処理。 responseに返ってきたjsonが全て入ってる。
  for (const item of response.data) {
    // APIから取得できた本のリストにの各要素「item」について、このコンポーネント内で扱える形(構造体)に変換する。
    // 変換したあとでsetBooks()をつかって「books」stateに入れていく。
    // 表示する「ステータス」の値をAPIから返ってきた「last_activity」を使って動的生成する。
    // 邪魔なら一旦見なかったことにしたほうが良い。
    var statusValue: string;
    if (item.last_activity && item.last_activity.type === "borrow") {
      statusValue = "持出し中" + " - " + item.last_activity.user.employee_id
    }
    else {
      statusValue = "利用可"
    }
    // 「ステータス」の動的生成ここまで。
    // 「newBook」を作る。これは取得できた1つのitemを構造体に変換したもの。
    const newBook = { id: item.id, title: item.title, note: item.note, status: statusValue }
    // 変換した構造体を「books」に追加する。
    setBooks((oldBooks) => [...oldBooks, newBook]) // [...oldBooks, newBook] は「oldBooks全てとnewBookからなる新しい配列」の表現。
  }
})
const promiseForAfterError = promiseForAfterApiCall.catch((error) => { setHasError(true); }) // axiosのgetでエラーが起きたときの処理
const promiseForAfterAll = promiseForAfterError.finally(() => { setIsLoaded(true); }) // エラーが起きたかどうかに関わらず、axiosのgetが終わったあとの処理

このコードを解説しながらばらしていきます。お約束ですが書いてる本人はろくに理解してないので変なところがあったらググってください。

const promiseForApiCall = axios.get(API_BASEURL + "books/") // axiosのget処理を開始する(非同期処理の開始を予約する)

ここではAPIに対してgetリクエストを行うことを「約束」させます。 promiseForApiCallには約束したことを示すオブジェクトが戻されてきて入ります。

const promiseForAfterApiCall = promiseForApiCall.then((response) => { // axiosのget処理がうまくいったときの処理。 responseに返ってきたjsonが全て入ってる。
  for (const item of response.data) {
    // (長いので中略)
    setBooks((oldBooks) => [...oldBooks, newBook]) // [...oldBooks, newBook] は「oldBooks全てとnewBookからなる新しい配列」の表現。
  }
})

ここでは、さっき戻ってきた「getを約束したよ」を示すオブジェクトpromiseForApiCallに対し、「約束してた処理が終わったらその結果を使って次にこの処理をすることを約束してね」というお願いをしています。 今回した約束を示すオブジェクトはpromiseForAfterApiCallに入れておきます。

注意:ここから下はあってるかわかんないです。すみません。指摘があればコメントお願いします。)

const promiseForAfterError = promiseForAfterApiCall.catch((error) => { setHasError(true); }) // axiosのgetでエラーが起きたときの処理

ここでは、「さっきした約束がもしエラーだったらこういう処理をしてね」という約束をしています。 「さっきした約束」にはpromiseForApiCallでした約束とpromiseForAfterApiCallでした約束の両方が含まれているみたいです。なので、APIを呼んだときのエラー処理は全てここで処理してしまえば良いようです。楽です…。(今回はsetHasError(true);だけをやっています)

const promiseForAfterAll = promiseForAfterError.finally(() => { setIsLoaded(true); }) // エラーが起きたかどうかに関わらず、axiosのgetが終わったあとの処理

最後に、ここでは「さっきした一連の約束が終わったときに最後にこの処理をしてね」という約束をしています。

最初の実装ではこの一連の流れを再代入無しにつなげて書いたものです。理解していただけると再代入なしのほうが意味的にしっくり来るのではないかと思います。 *1

以上です。

*1:書いてる当人は書いている最中にやっと5割ぐらい理解しました。アウトプットするのって大事ですね。