找了幾個 API 服務使用:
這裡使用的是 Dog API 的接口,透過 GET 來取得遠端資料。
早期取得遠端資料 GET 方法,使用 XMLHttpRequest
在早期如果要透過 AJAX 取得遠端資料,如不使用框架所使用的方式是 XMLHttpRequest 的行為方法,在使用上除了可讀性不高也不好唯護。
See the Pen 使用 XMLHttpResuest GET 執行 AJAX by Jimmy_Wu (@Jimmy_Wu) on CodePen.
什麼是 Fetch API 與 Promise
fetch:操作 AJAX 的方法
fetch() 的使用概觀
Fetch 中文翻譯為取。
MDN 文件 – 使用 Fetch 文件中指出:
Fetch API 提供了一個 JavaScript 接口,用於訪問和操縱 HTTP 管道的一些具體部分,例如請求和響應。它還提供了一個全局 fetch() 方法,該方法提供了一種簡單,合理的方式來跨網絡異步獲取資源。
fetch() 的使用有幾個特點
- 以 ES6 的 Promise 作回應。
- .then() 作為下一步。
- .catch() 作為錯誤回應 (404, 500…) 的 HTTP 狀態回應。
- return 回傳為 ReadableStream 物件,需使用不同資類型對應方法,取得資料物件。
fetch() 透過 .then() 的 Promise,接收後傳到下個 .then()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const urlPath = 'https://dog.ceo/api/breeds/image/random'; // 可運行 API 網址 fetch(urlPath, {}) .then((response) => { // 這裡會得到一個 ReadableStream 的物件 console.log('response', response); // 可以透過 blob(), json(), text() 轉成可用的資訊 return response.json(); }) .then((jsonData) => { console.log('jsonData =>', jsonData); }) .catch((err) => { err !== undefined ? console.log('err => ', err) : ''; }); |
透過 fetch() API 方法執行 AJAX 行為,第一個 .then() 取得的是整個主機的回應狀態,所取得的 ReadableStream 物件使用 .json() 方法,將純文字資料轉換成 JSON 資料格式,透過 return 回傳給下一個 .then()。
第二個 .then() 是由上方第一個 .then() 所回傳出來的 JSON 資料,接收到之後就可直接將傳入的參數 jsonData 印出,就可取得一個物件資料格式。
Fetch API 的 Response 物件 ReadableStream 實體
在 Fetch API 的 Response 物件 ReadableStream 實體無法在此階段取得資資料內容,但 ReadableStream 實體下還有對應的解析方法取得裡面的資料。
- arrayBuffer()
- blob()
- formData()
- json()
- text()
text()
在 Response 物件使用 .text() 方法查看資料,會取得的是純文字的資料。
json()
在 Response 物件使用 .json() 方法查看資料,所查看的就是當用的資料格式,可在 JavaScript 進行相關的操作。
blob()
資料轉為 blob 物件,這樣的方式是圖片的轉換方式 (這裡的圖片並非指圖片路徑,而是圖片檔案本身)。
JavaScript ES6 Fetch() 執行 AJAX
See the Pen 使用 JS ES6 Fetch 執行 AJAX by Jimmy_Wu (@Jimmy_Wu) on CodePen.
Promise:接收 AJAX 的執行結果或狀態
Promise 中文翻譯為承諾。
MDN 文件 – Promise 文件中指出:
Promise 物件代表一個即將完成、或失敗的非同步操作,以及它所產生的值。
AJAX 與 Promise 的關係
AJAX 全名為 Asynchronous JavaScript and XML (非同步的JavaScript與XML技術)。
AJAX 應用可以僅向伺服器傳送並取回必須的資料,並在客戶端採用 JavaScript 處理來自伺服器的回應。在伺服器和瀏覽器之間交換的資料大量減少伺服器回應更快了。同時,很多的處理工作可以在發出請求的客戶端機器上完成,因此 Web 伺服器的負荷減少的情形下,自然可以增加使用都端與主機交換資料的效率。
Ajax 是屬於一個透過 JavaScript 技術名稱,用於取得遠端資料。
Promise 則是一個語法,專門用來處理非同步行為,並不是專門用來處理 Ajax 使用,所以兩者是不同的。
Promise 與 Async、Await 的關係
Promise 是用來優化非同步的語法,Async、Await 可以基於 Promise 讓非同步的語法的結構,類似於同步語言,讓可讀性與易管理增加。
Promise 改善 JavaScript 非同步的語法結構
非同步與單線程的執行緒 (單執行緒) 的執行順序
JavaScript 是屬單線程的執行緒 (單執行緒) 一次僅能做一件事情的方式執行,
如遇非同步的事件時,會將非同步事件 (語法) 移到程式碼最後方執行,待所有原始碼運行完後才會執行非同步的事件。
單線程的執行緒 (單執行緒) 執行順序約為:1.開始、2.程式碼結束、3.非同步事件 (最後執行)
1 2 3 4 5 6 7 8 9 | console.log('開始'); setTimeout(() => { console.log('非同步事件'); }, 0); console.log('程式碼結束'); // setTimeout 所定義的時間為 0,但因為是屬於非同步事件,因此還是會在其他原始碼運行完以後才執行。 |
AJAX 行為也一樣,需確保擷取到遠端資料才繼續往下執行時,如果程式碼是依序撰寫的方式,就會無法正確呈現資料。
下面範例使用 Promise base 的 Ajax 函式庫 axios 進行一下錯誤的示範:
1 2 3 4 5 6 7 8 9 10 11 | let data = {} console.log('開始'); axios .get('https://randomuser.me/api/') .then(function(response) { data = response; }); console.log(data); |
最後的 console.log(data);,這裡因非同步事件取資料往後執行,所以不會取得 AJAX 的資料,而是取得 data = {} 預設資料格式。
data = response; 透過賦值的方式,在確任接到 AJAX 資料時也指到最前面的 let data 全域變數中,要查看資料需要在 .then() 執行 console.log()。
Promise 結構及狀態
Promise 為建構函式需透過 new 新物件,才可使用物件下的方法
Promise 本身是一個建構函式,函式也是屬於物件的一種,可附加其它屬性方法在上。
透過 console.dir() 的結果可以看到 Promise 可以直接使用 all、race、resolve、reject 的方法,寫法如下:
- Promise.all()
- Promise.race()
- Promise.resolve()
- Promise.reject()
Promise 建構函式,可使用 new Promise() 創出物件,在原型方法 ( prototype 下) 包含 .then()、 .catch()、 finally() 方法,這些方法都要新產生物件才能呼叫使用。
1 2 3 4 5 | const p = new Promise(); p.then(); // Promise 回傳正確 p.catch(); // Promise 回傳失敗 p.finally(); // 非同步執行完畢(無論是否正確完成) |
Promise 建構函式建立時需傳入函式為為參數 (executor function),函式裡可包函 resolve (解決) 與 reject (拒絕) 方法選其一回傳做為 Promise 事件結束
1 2 3 4 | new Promise(function(resolve, reject) { resolve(); // 正確完成的回傳方法 reject(); // 失敗的回傳方法 }); |
Promise 的狀態
Promise 關鍵在處理非同步的事件,過程中包含著不同進取度狀態,如下:
- pending:事件運行中尚未取得結果。
- resolved:事件已執行完畢且成功操作,回傳 resolve 做為結果。(該承諾已經被實現 fulfilled)
- rejected:事件已經執行完畢但操作失敗,回傳 rejected 的結果。
進入 fulfilled 或 rejected 就算完成後不會再改變,
Promise 中會使用 resolve 或 reject 回傳結果,
並在調用時使用 then 或 catch 取得值。
1 2 3 4 | function promise() { return new Promise((resolve, reject) => {}); } console.dir(promise()); |
使用 console.dir() 展開函式,可見二個屬性:
- [[PromiseStatus]]: "pending" (表示目前的進度狀態)
- [[PromiseValue]]: undefined (表示 resolve 或 reject 回傳的值)
Promise() 執行 reject ,取得執行結果。
1 2 3 4 5 | function promise() { return new Promise((resolve, reject) => {reject('失敗');}); } console.dir(promise()); |
執行完函式直接 reject('失敗'),最終也能取得 rejected 的狀態及值。
參考資料
- 30天走訪Progressive Web Apps(PWAs)系列 第 8 篇 | Day8-Fetch API與Promise 使用方式介紹
- JavaScript Promise 全介紹
- 卡斯伯 Blog – 前端,沒有極限 | 鐵人賽:ES6 原生 Fetch 遠端資料方法
- 卡斯伯 Blog – 前端,沒有極限 | Javascript Promise 範例
- 從 Promises/A+ 看異步流控制
其他 Async function 與 Await 的使用參考:
卡斯伯 Blog – 前端,沒有極限 | Async function / Await 深度介紹