2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Page 1 of 2

非同期処理は、すぐに答えが返らない仕事を待つための書き方です。

今回はVue 3で、ソードの情報をファイルから読み込み、届いたら画面へ表示する流れを整理してみます。まずは「普通に書くと何が起きるのか」と、async / await を使う理由を小さな例で確認したいです。

まだ途中段階なので、勘違いなどあれば教えていただけるとうれしいです。

今日やったこと

データが届く前に使おうとして失敗する例と、待ってから使う例を比べてみました。

すでに値がある場合は、上から順に書けばすぐ使えます。

// 最初からソード情報がある
const sword = { name: 'Iron Sword' }

// すぐにソード名を表示できる
console.log(sword.name)

ただし、ファイルやAPIからデータを受け取る場合は、取りに行ってから届くまで少し時間がかかります。

今回は、ゲーム内で鍛冶屋にソードを作ってもらう場面に置き換えて考えます。

  • ソードを作ってもらう
  • 完成通知を待つ
  • 完成したソードを受け取る
  • 装備欄へ表示する
キャラクター 「完成通知が出る前に装備欄を開いても、何も増えていないやつだ。受け取ってから次のことをするんだね」

今回のVue 3プロジェクトでは、public フォルダに sword.json を置く想定にします。

{
  "name": "Iron Sword"
}

このファイルは、ブラウザから /sword.json として読み込めます。

つまずいたところ

データを取りに行った直後に使おうとすると、まだ届いていないためエラーになります。

普通に書くと失敗する

最初は、次のように上から順に書けばよいと思っていました。

<script setup>
// ソード情報を読み込む関数
const loadSword = () => {
  // sword.jsonを取りに行く
  // ただし、この時点ではファイルはまだ届いていない
  const response = fetch('/sword.json')

  // responseはPromiseなので、json()を使えずエラーになる
  const sword = response.json()

  // この行まで処理は進まない
  console.log(sword.name)
}

// 読み込み処理を始める
loadSword()
</script>

このコードでは、次のようなエラーになります。

TypeError: response.json is not a function

fetch() の直後に入る response は、ファイルの返事そのものではありません。

「今からファイルを取りに行きます。返事はあとで返します」というPromiseが入っています。Promiseには json() がないため、response.json() を呼ぶとエラーになります。 developer.mozilla

キャラクター 「ソードを受け取る前に、完成品の名前を見ようとしている状態か。まだ手元にないから、使えないんだね」

データが壊れるというより、届く前のデータを使おうとしてエラーになる、ということが今回のポイントでした。

async / await を使う

await を使うと、ファイルが届くまで待ってから次の行へ進めます。

ただし、await を使う関数には async を付ける必要があります。

<script setup>
// asyncを付けると、この関数の中でawaitを使える
const loadSword = async () => {
  // ファイルが届くまで待つ
  // 待ち終わると、responseにはファイルの返事が入る
  const response = await fetch('/sword.json')

  // ファイルの中身を読み終わるまで待つ
  // 待ち終わると、swordにソード情報が入る
  const sword = await response.json()

  // ソード情報を受け取った後なので、名前を使える
  console.log(sword.name)
}

// 読み込み処理を始める
loadSword()
</script>

今回のコードでは、待つ場所が2つあります。

  1. fetch() でファイルの返事が届くまで待つ
  2. response.json() でファイルの中身を読み終わるまで待つ

fetch() はPromiseを返し、response.json() もJSONを読み終えた後にPromiseで結果を返します。 developer.mozilla

今の理解(メモ)

今は、async / await を「届いていないデータを、届いてから使うための書き方」と考えています。

  • fetch():ファイルやAPIへデータを取りに行く
  • Promise:今は結果がなく、あとで返事が来るもの
  • asyncawait を使う関数に付ける
  • await:結果が届くまで待つ
  • response.json():届いた返事の中身を読む
  • ref:画面に表示したい値を覚えておく箱
  • onMounted():画面が表示された後に処理を始める

Promiseは、あとで分かる値を表すものです。 今回はPromiseを自分で作らず、fetch() が返してくれるPromiseを await で待つ形にしました。 developer.mozilla

Vue 3では、受け取ったソード情報を ref に入れると、画面の表示へつなげられます。

<script setup>
// onMountedは、画面が表示された後に処理を始めるために使う
// refは、画面に表示したい値を覚えておくために使う
import { onMounted, ref } from 'vue'

// 最初はソード情報がないのでnullを入れる
const sword = ref(null)

// 読み込み中かどうかを覚えておく
const isLoading = ref(true)

// ソード情報を読み込む関数
const loadSword = async () => {
  // ファイルが届くまで待つ
  const response = await fetch('/sword.json')

  // 読み終わったソード情報を、画面用の箱へ入れる
  sword.value = await response.json()

  // 読み込みが終わったので、読み込み中をfalseにする
  isLoading.value = false
}

// 画面が表示された後に、ソード情報の読み込みを始める
onMounted(loadSword)
</script>

<template>
  <main>
    <!-- データを待っている間だけ表示する -->
    <p v-if="isLoading">ソードを作っています...</p>

    <!-- データが届いた後に表示する -->
    <section v-else-if="sword">
      <h1>完成したソード</h1>

      <!-- templateの中では.valueを書かずにsword.nameを使える -->
      <p>{{ sword.name }}</p>
    </section>
  </main>
</template>

このコードでは、最初に「ソードを作っています...」と表示されます。ファイルを読み終えると、その表示が消え、Iron Sword が画面に出ます。

ゲームで考えると、作成中は装備欄に完成品を出さず、受け取った後で名前を表示する流れに近そうです。

キャラクター 「`await` で待って、届いたソードを `ref` に入れる。そうすると画面の表示も変わるんだね」

onMounted() は、コンポーネントが画面へ表示された後に呼ばれる処理を登録するために使えます。 ref の値を更新すると、Vueが画面の表示も更新してくれます。Vue.js公式ドキュメント ja.vuejs

次にやりたいこと

まずは、受け取ったデータに合わせて画面を少しずつ増やしたいです。

  • ソードの画像もJSONへ追加して、画面に表示する
    名前だけでなく、アイテムカードのような見た目にしてみたいです
  • try...catch を追加し、ファイルが読めなかったときの表示を作る
    読み込みに失敗したことを、画面でどう伝えるか確認したいです
  • ソードを複数用意し、v-for で一覧表示する
    持ち物一覧のように、データ数に合わせてカードを並べてみたいです
  • ボタンを押したときにデータを読み込み直す
    利用者の操作で、画面の内容が変わる流れを試したいです
  • 別の通信方法でも同じ流れを試す
    「待つ → ref に入れる → 画面に表示する」という形がどう使えるか確認したいです

今日の整理

今回は、「普通に書くとエラーになる理由」と「待つ書き方」の違いを整理しました。

書き方 response に入るもの その後どうなるか
const response = fetch(...) Promise response.json() でエラーになる
const response = await fetch(...) ファイルの返事 response.json() を使える
const sword = await response.json() ソード情報 sword.name を使える

Vue 3で使うときは、次の順番を覚えておけばよさそうです。

順番 コード 何をしているか
1 const sword = ref(null) 画面に出すデータの箱を作る
2 const loadSword = async () => {} 待つ処理を書く関数を作る
3 await fetch(...) ファイルが届くまで待つ
4 sword.value = await response.json() 届いたデータを箱へ入れる
5 {{ sword.name }} 箱の中のデータを画面に出す

おわりに

非同期処理は、言葉だけだと難しく感じていましたが、「データが届く前に使うとエラーになる」と分かると、await が必要な理由を少し理解できました。

今回は、fetch() が返してくれるPromiseを待つ形にしました。次は、受け取ったデータを一覧として表示したり、読み込みに失敗したときの画面を作ったりして、画面の動きをもう少し試してみたいです。

学習中の内容なので、勘違いや理解不足もあると思います。もし気になる点があれば教えてもらえるとうれしいです。

 注意事項
 本ブログに掲載している内容は、私個人の見解であり、
 所属する組織の立場や戦略、意見を代表するものではありません。
 あくまでエンジニアとしての経験や考えを発信していますのでご了承ください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?