非同步處理是用來等待那些不會立刻回傳答案的工作的寫法。
這次我用 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>
這段程式中有兩個需要等待的地方。
fetch() 等待檔案回應到達response.json() 等待檔案內容讀取完成fetch() 會回傳 Promise,而 response.json() 也會在 JSON 讀取完成後,以 Promise 的方式回傳結果。developer.mozilla
我現在把 async / await 理解成「讓還沒到的資料,等到到了再使用」的寫法。
fetch():去檔案或 API 取得資料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
接下來我想根據收到的資料,慢慢增加畫面內容。
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 中使用時,記住下面這個順序應該就夠了。
const 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