Javascript Fetch API
Javascript Fetch API
簡介 |
Fetch API是WHATWG 近年來推動的新標準,其擁有良好的設計、Promise 的實作、API 相容性 (e.g., Service Worker、Cache) …,儘管規範尚未穩定,且許多功能仍處實驗中,仍令許多開發者趨之若鶩 ! |
作者 |
詹國忠 |
1.前言
l Fetch API是WHATWG 近年來推動的新標準,其擁有良好的設計、Promise 的實作、API 相容性 (e.g., Service Worker、Cache) …,儘管規範尚未穩定,且許多功能仍處實驗中,仍令許多開發者趨之若鶩 !
2.基本使用
l 不同於 jQuery 需額外載入,Fetch API 是標準的 Web API,因此能夠 直接使用。但別忘記啦 ! 新的 API 也說明 瀏覽器相容性 可能不普及,
l MDN 提供了相容性表格
可謂慘不忍睹,像 iOS safari 就要 10.1 以上才能使用,不過沒關係,可以使用 自動補完函式庫 (polyfill) 解決,polyfill-service 是個方便簡單的選擇 :
(在 HTML 的 head 元素內加入 <script src=…)
<head>
<title>Fetch API Demo</title>
<meta charset="utf-8">
<script src="//cdn.polyfill.io/v2/polyfill.min.js?features=fetch"></script>
</head>
3.簡單的fetching範例
3.1Fetch()
首先,呼叫全域的 fetch 方法,參數放置的是 目標 URI :
(當然,還有還有其他實例方式 — — 可選參數、多載函式…)
fetch(
'https://gank.io/api/random/data/福利/20')
fetch() 函式會回傳一個 Promise,並在解析/完成 (resolve) 後,回傳 Response 物件,
因此,能直接以 .then(onFulfilled, onRejected)
串接解析 完成 或 拒絕 的回調函式,
且能使用 Response 物件 提供的 json()
方法,將回應解析為 JSON 物件 !
fetch(
'https://gank.io/api/random/data/福利/20')
.then(
function (res) {
return res.json();
})
.then(
function (data) {
jsonHandler(data);
});
這樣就可以完成一個最基本的fetch範例 !
3.2Async/Await
說到 Promise,就不能忘記Async/Await !
再進一步改寫,跟callback函式說掰掰 ~
async function fetchGank() {
let res = await fetch('https://gank.io/api/random/data/福利/20');
// 等待 parse json
let data = await res.json();
jsonHandler(data);
}
需注意的是,json() 方法回傳的也是 Promise,在其解析完成後,會回傳 JSON 物件。
因此 務必 加上 await 等待解析的結果,否則 data 會是 undefined !
3.3二進制回應處理
Fetch 處理二進制回應,也是毫無壓力,直接使用 res.blob(),回應就解析完成 :
async function blobDemo() {
try {
let res = await fetch('Nobita.jpg');
if (res.ok) {
// 等待 parse blob
let blob = await res.blob();
let img = document.createElement("img");
let link = document.createElement("a");
// 建立 blob URI (Object URI)
var blobUri = window.URL.createObjectURL(blob);
img.src = blobUri;
link.href = blobUri;
link.innerHTML = "Download"; // 下載提示文字
link.download = "Nobita.jpg"; // 檔案名稱
// 新增 img 與 a 元素至頁面
document.body.appendChild(img);
document.body.appendChild(link);
} else {
let text = await res.text();
console.log(text);
}
} catch (e) {
console.log(e);
}
}
4.關注點分離(Separation of Concerns)
不同於 XMLHttpRequest 或 jQuery Ajax,所有職責都在單一類別。
Fetch API 擁有良好的 關注點分離 (Separation of Concerns, SOC),除了已經見過的 全域 fetch 方法,以及回應的 Response 物件,尚有 Body、 Headers 以及 Request 等介面。
其實,我們也已使用過 Body 介面了 !
在上例中,在回應的 Response 物件上,使用 res.json() 來讀取 訊息主體 (Body) 並解析為 JSON 物件,
那就是 Body 介面所提供的屬性與方法喔 !
4.1Body
4.1.1串流 (Stream)
訊息主體 (Body) 被 Request 與 Response 所實作,內容是 Byte Stream 的串流形式,
因此,一旦透過 json()、text()、formData() 或 arrayBuffer() 讀取後,Body 的 bodyUsed 唯讀屬性,會設置為 true。
這代表:
Request、Response 物件,只能被讀取一次 !
以下範例,欲分別以 json 與 text 的方式讀取回應:
async function willError() {
let res = await fetch('https://gank.io/api/random/data/福利/20');
let data = await res.json();
let text = await res.text();
console.log(data);
console.log(text);
}
看似合理,卻會拋出 Body has already been consumed. 錯誤 ! 使用上需多加注意。
4.1.2副本 (Clone)
復用 請求 與 回應 Body 是很常見的需求,
解決 只讀一次 最簡單的方式,就是做一個 副本 啦 !
因此 Request 與 Response 介面,都提供了 clone 方法。
接著,改寫一下上方範例 :
async function WillSuccess() {
let res = await fetch('https://gank.io/api/random/data/福利/20');
// 製作 Response 物件副本
let resCopy = res.clone();
let data = await res.json();
let text = await resCopy.text();
console.log(data);
console.log(text);
}
成功 !
4.2Headers
Headers 表頭,是 Fetch API 中最簡單的介面了 !
首先使用 Headers 建構元實例物件 :
var myHeaders = new Headers(init);
init 參數是可選的,可以是 ByteString 形式的物件 :
var myHeaders = new Headers({
'Content-Type': 'image/jpeg',
'User-Agent': 'Hellow, World!'
});
也可以在實例完成之後,用 append() 方法添加:
var headers = new Headers();
headers.append('How-Are-You', 'I\'m fine, Thank you.');
欲讀取 回應 (Response) 的 表頭,則是 :
var myHeaders = response.headers;
1.1
4.3Request
在介紹 Request 之前,先看看完整的 fetch() 語法 :
fetch(input[, init]);
再來看看 Request() 的語法 :
Request(input, input[, init]);
沒錯,除了一個回傳值是 Promise<Response>,另一個是 Request,他們的實例方式一模一樣 !
4.3.1Input
input 參數,除了 基本使用 介紹過的 目標 URI 以外,也能是一個 Request 物件 !
還記得剛才用 clone 方法,解決 Body 只讀一次 的問題嗎 ?
也能如法炮製,直接使用建構元製作副本喔 !
var myRequest =
new Request(
'https://example.com');
// 製作 Request 物件副本
var myRequest2 =
new Request(myRequest);
5.完整範例
最後,統整之前提到的所有介面,一個完整的 POST 請求就出來了:
async function post() {
try {
// 設置表頭欄位 (以一個客製化表頭為例)
var headers = new Headers({'x-just-test': 'This is a test.'});
headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
// 準備 資料酬載 (Payload)
var data = {name: "勝", city: "Taipei"};
// 以 application/x-www-form-urlencoded 的內容類型送出
// 酬載 (Payload) 需使用百分比編碼
var encodedData = encodeFormData(data);
// 請求參數 init:
//
// 請求方法 POST
// 設置表頭
// 將編碼後的酬載 置於訊息主體 (Message Body) 中
var myInit = {
method: 'POST',
headers: headers,
body: encodedData
};
// 實例請求 -- Request,設置 目標 URI 與 請求參數 init
var myRequest = new Request('demo_test_post.asp', myInit);
// 非同步執行 fetch
let res = await fetch(myRequest);
// 判斷回應狀態碼
if (res.ok) {
alert(await res.text());
} else {
let text = await res.text();
console.log(text);
}
} catch (e) {
console.log(e);
}
}
6.Fetch vs. jQuery
不同於 jQuery,無論回應狀態碼是 4xx 的客戶端錯誤 或 5xx 的伺服端錯誤 ,
Fetch 都視為 解析成功 (Resolved Promise),僅在網路錯誤 或 請求被拒 才會拒絕 Promise,
因此實務開發時,回應狀態碼的判斷 必不能省 :
if (res.ok) {
...成功...
}
其等價於 :
if (200 <= res.status && res.status <= 299) {
...成功...
}
7.總結
Fetch 是用於獲取資源的低階 API,涵蓋比 XMLHttpRequest 還全面的範疇,儘管目前仍缺少對於請求進度的處理 (e.g., 中斷),以及各家瀏覽器引擎相容性的問題,但仍可期待其未來的潛力。