跳至主要内容

網路請求與非同步

9/19/2023 發布

一般程式語言都是逐行執行的,因此每一個操作都要等待上一個操作結束,
這就是「同步」的概念。

JS 是單執行緒的程式語言,這表示一次只能執行一段程式碼,
但是非同步的機制可以把執行的過程弄得很像是一次能做很多事一樣。

Callback

callback function 是構成非同步機制的要件,比如 forEach、map 方法等等都會用到,
callback 本身會被當成參數傳入,並在這些方法的空間裡面執行,格式類似這樣:

function fn(callback) {
callback();
}

內建方法的參數通常都是固定好的,這些方法會定義參數的內容,
假設在呼叫 forEach 方法時亂傳變數進去,實際上也不會真的傳入,
而是照它定義好的規則逐一解析 callback 參數。


Event Loop

瀏覽器也是一個執行 JS 程式碼的環境,
JS 本身的程式,如上面舉例的陣列方法,就會在環境內先執行,
如果是瀏覽器的 Web API,就會丟到 Web API 的空間解析,
解析完後再透過 event loop 的檢查機制,把解析完的程式碼丟回環境內執行。

Web API 包含 setTimeoutfetch 等等...我們最熟悉的 console.log 也是!
回顧一開始介紹變數宣告的時候為什麼不推薦 var,
因為這些解析完的 callback 在後續回到全域環境時候,
容易因為 var 意外造成的污染使得 callback 這時存取到不正確的東西。


Promise

是一種包裝好的特別物件,在網路請求時經常會用到,
內容通常是請求的結果,成功或失敗都會有對應的資料,
沒辦法直接用 console.log 看到 Promise 物件的內容,
也無法直接取出使用,需要用後綴 then() 的方式取出資料:

function getData(apiUrl) {
axios
.get(apiUrl)
.then((res) => console.log(res))
.catch((error) => console.log(error));
}

axios.get 方法裡帶入網址後會回傳一個 Promise 物件,
.then 裡面的 callback 預設參數就是伺服器回應的資料,
這時才能用 console.log 查看內容,或是用外層的變數儲存起來。


切割任務與排查錯誤

非同步的機制可以把需要等待時間的 callback 延後直到等待時間到了才執行,
主程式則可以先往下繼續執行。

例如進到購物網站時,商品列表還在讀取,但是點擊網頁其他的地方依然正常動作,
因為非同步的機制把網頁各區塊的任務切割,
不會因為某個地方載入太慢或失敗,導致網頁的其他功能失效。

非同步函式回到主程式的時間也不一定,
可以發現購物網站裡面每張圖載入的順序或時間大多都不是固定的,
因為非同步函式的結果往往會在主程式的某個行程中突然插隊進來。

網路請求並不是每次都會成功,
有時會遇到伺服器的問題,或是程式碼本身的撰寫有誤,
所以 .then() 之外也需要 .catch() 來捕捉錯誤訊息,
.catch() 只會在有錯誤發生時執行。

要注意的是 .then 裡面也有可能拿到錯誤通知!
因為發出的請求可能是連線成功的,但伺服器在解析請求時可能有些不合法的內容,
例如打錯密碼、註冊時用了重複的 email 等等。

排查錯誤時必須看後端怎麼定義 status 以及前端的 AJAX 技術是什麼,
如原生的 fetch 需要手動檢查 response.ok
否則只有 network error 才會在 fetch 的 catch 捕捉到,
所以 .then.catch 都要下才能知道錯誤訊息的來源!


AJAX

AJAX 就是非同步網路請求的技術,
比如原生的 fetct,或是 axios、jQuery 這類好用的套件都是一種 AJAX 技術。

原生的 fetch 雖然可以直接使用,不過要寫的東西就相對複雜, 以剛剛的範例來說:

function getData(apiUrl) {
fetch(apiUrl)
.then((res) => res.json())
.then((result) => console.log(result))
.catch((error) => console.log(error));
}

第一個 then 拿到的參數通常不是結構化的資料,要透過內建的 .json() 方法,
再丟出一個 Promise 物件,然後加上第二個 then 取出來...就是這麼麻煩 XD

axios 則簡化了這些流程,從 axios 回傳的 Promise 物件,
只要用一次 then 就可以取出,HTTP method 也可以透過點記法直接叫出對應的方法。

此外 axios 也有一些好用的方法,
比如可以直接設定好 API 的主網域,這樣後續在撰寫請求邏輯時不用寫一長串的網址,
或是取得 token 後可以寫入設定,之後的所有請求都會自動帶入此 token:

axios.defaults.baseURL = "https://api.example.com";
axios.defaults.headers.common["Authorization"] = AUTH_TOKEN;

參考資料