非同步處理是用來等待那些不會立刻回傳答案的工作的寫法。

這次我用 Vue 3 來做一個流程:從檔案讀取遊戲世界裡劍(sword)的資訊,等資料到達後再顯示在畫面上。先想確認的是,「如果直接照一般寫法會發生什麼事」,以及使用 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>

這段程式中有兩個需要等待的地方。

  1. fetch() 等待檔案回應到達
  2. response.json() 等待檔案內容讀取完成

fetch() 會回傳 Promise,而 response.json() 也會在 JSON 讀取完成後,以 Promise 的方式回傳結果。developer.mozilla

目前的理解(筆記)

我現在把 async / await 理解成「讓還沒到的資料,等到到了再使用」的寫法。

  • fetch():去檔案或 API 取得資料
  • Promise:現在還沒有結果,但之後會回傳
  • async:加在要使用 await 的函式前面
  • 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(...)Promiseresponse.json() 會出錯const response = await fetch(...)檔案回應可以使用 response.json() const sword = await response.json()劍資訊可以使用 sword.name 來取得在 Vue 3 中使用時,記住下面這個順序應該就夠了。

順序程式碼在做什麼1const sword = ref(null)建立顯示在畫面上的資料容器2const loadSword = async () => {}建立處理等待的函式3await fetch(...)等待檔案到達4sword.value = await response.json()把收到的資料放進容器5{{ sword.name }}把容器裡的資料顯示到畫面上結語

一開始我覺得非同步處理很抽象,但理解成「在資料還沒到之前就使用會出錯」,就比較能明白為什麼需要 await

這次是先處理 fetch() 回傳的 Promise。接下來我想再試著把收到的資料顯示成列表,或做出讀取失敗時的畫面,讓畫面的互動再更完整一些。

因為這還是在學習中的內容,應該還有理解不足的地方。如果你有任何覺得怪怪的地方,也很歡迎告訴我。

注意事項
本部落格所刊載的內容,僅代表我個人的見解,
並不代表所屬組織的立場、策略或意見。
以上內容僅是以工程師身分分享經驗與想法,敬請見諒。


原文出處:https://qiita.com/yuki_crossroad/items/a99f0d9f0db6ebd87c00


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝10   💬4   ❤️1
201
🥈
我愛JS
💬1  
6
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登