javascript fetch api

Javascript Fetch API

詹國忠 2017/12/01 11:00:00
1318

Javascript Fetch API


簡介

Fetch API是WHATWG 近年來推動的新標準,其擁有良好的設計、Promise 的實作、API 相容性 (e.g., Service Worker、Cache) …,儘管規範尚未穩定,且許多功能仍處實驗中,仍令許多開發者趨之若鶩 !

作者

詹國忠


1.前言

l   Fetch APIWHATWG 近年來推動的新標準,其擁有良好的設計、Promise 的實作、API 相容性 (e.g., Service WorkerCache) …,儘管規範尚未穩定,且許多功能仍處實驗中,仍令許多開發者趨之若鶩 !

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
 
這代表:

RequestResponse 物件,只能被讀取一次 !

 
以下範例,欲分別以 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., 中斷),以及各家瀏覽器引擎相容性的問題,但仍可期待其未來的潛力。

詹國忠