JavaScript Fetch API 使用教學

自從 Fetch API 問世以來,我們就能使用漂亮的語法發送 HTTP Request 或取得網路資料,這篇文章將會分享我自己常用的 Fetch 方法 ( GET、POST、搭配 await 或 promise.all...等 ),隨著瀏覽器的普遍支援,也就不太需要使用 XMLHttpRequest 或 jQuery AJAX,程式碼也就更加簡潔乾淨囉~

Fetch 基本用法

fetch()是一個全域的方法,包含了需要 fetch 的網址和對應的屬性設定 ( 例如 method、headers、mode、body...等,最基本的寫法屬性不一定要填 ),執行之後會送出 Request,如果得到回應就會回傳帶有 Response 的 Promise 物件,使用 then 將回傳值傳遞下去。

fetch('網址')
.then(function(response) {
    // 處理 response
}).catch(function(err) {
    // 錯誤處理
});

舉例來說,前往 中央氣象局開放資料平台可以取得許多氣象資料,下面的範例使用 局屬氣象站-現在天氣觀測報告,複製 JSON 格式的連結 ( 需要註冊登入才能看得到連結 ),因為檔案為 json 格式,所以在 fetch 取得檔案之後,透過json()的方法處理檔案,接著傳遞到下一層,就能顯示出「高雄市的即時氣溫」。

fetch('局屬氣象站-現在天氣觀測報告網址')
.then(res => {
    return res.json();
}).then(result => {
    let city = result.cwbopendata.location[14].parameter[0].parameterValue;
    let temp = result.cwbopendata.location[14].weatherElement[3].elementValue.value;
    console.log(`${city}的氣溫為 ${temp} 度 C`); // 得到 高雄市的氣溫為 29.30 度 C
});

Fetch 的 Request 屬性

以下列出 Fetch 常用的的 Request 屬性。( 更多屬性請參考 fetch Request )

屬性 設定值
url 第一個參數,一定要填的項目,代表需要 fetch 對象的網址
method GET、POST、PUT、DELETE、HEAD ( 預設 GET )
headers 要求相關的 Headers 物件 ( 預設 {} )
mode cors、no-cors、same-origin、navigate ( 預設 cors )
referrer no-referrer、client 或某個網址 ( 預設 client )
credentials omit、same-origin、include ( 預設 omit )
redirect follow、error、manual ( 預設 manual )
cache default、no-store、reload、no-cache、force-cache ( 預設 default )
body 要加到要求中的內容 ( 如果 method 為 GET 或 HEAD 則不設定 )

Fetch 的 Response 屬性

以下列出 Fetch 常用的 Response 屬性。( 更多屬性和方法請參考 fetch Response )

屬性 設定值
headers 包含與 response 相關的 Headers 物件
ok 成功回傳 true,不成功回傳 false
status 狀態代碼,成功為 200
statusText 狀態文字,成功為 ok
type response 的類型,例如 basic、cors...等
url response 的 url

Fetch 的 Response 方法

以下列出 Fetch 常用的 Response 方法。( 更多屬性和方法請參考 fetch Response )

方法 設定值
json() 返回 Promise,resolves 是 JSON 物件
text() 返回 Promise,resolves 是 text string
blob() 返回 Promise,resolves 是 blob ( 非結構化物件資料,例如文字或二進位資料 )
arrayBuffer() 返回 Promise,resolves 是 ArrayBuffer ( 有多少 bytes )
formData() 返回 Promise,resolves 是 formData ( 表單資料對應的的 Key 或 Value )
clone() 建立 Response 的複製物件
error() 返回 Response 的錯誤物件

Fetch 的 Get 用法

Get 是 Fetch 最簡單的方法,使用 Get 必須要將 fetch 第二個參數裡的 method 設定為 get,如果遇到跨域問題,就搭配其他屬性例如 mode、credentials 來進行細部設定 ( 但針對非跨域的資料就沒用了 ),下方的範例我做了一個簡單的後端程式,透過 fetch 傳遞姓名和年紀的參數,就會看到後端回應一串文字。

const name = 'oxxo';
const age = 18;
 // 有興趣的可以使用下方的網址測試
const uri = `https://script.google.com/macros/s/AKfycbw5PnzwybI_VoZaHz65TpA5DYuLkxIF-HUGjJ6jRTOje0E6bVo/exec?name=${name}&age=${age}`;

fetch(uri, {method:'GET'})
.then(res => {
    return res.text();   // 使用 text() 可以得到純文字 String
}).then(result => {
    console.log(result); // 得到「你的名字是:oxxo,年紀:18 歲。」
});

Fetch 的 Post 用法

使用 POST 方法可以搭配 body 屬性設定傳遞參數,下方的測試網址,可以接收 name 和 age 所組成的物件,當網址接收到要求後,就會回應一個 json 物件,比較需要注意的是,如果是傳遞「中文」可能會出現亂碼,這時可以使用encodeURI來做轉碼,且要透過JSON.stringify來轉換成 string 方式傳遞。

const uri = 'https://script.google.com/macros/s/AKfycbxXH6aPsldTBeS41WRMnJEA5Xstc7cYMj6YimDO2Al7H6DkJZiz/exec';
fetch(uri, {
  method:'POST',
  body:encodeURI(JSON.stringify({
    name:'oxxo',
    age:18
  })),
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
  }
})
.then(res => {
    return res.json();   // 使用 json() 可以得到 json 物件
}).then(result => {
    console.log(result); // 得到 {name: "oxxo", age: 18, text: "你的名字是 oxxo,年紀 18 歲~"}
});

Fetch 搭配 async、await、promise.all

過去在 XMLHttpRequest 或 jQuery AJAX 的全盛時期,如果要確保每個 GET 或 POST 的要求,都要按照指定的順序進行,往往會用上一連串的 callback 輔助,但是當 callback 越來越多,程式碼也就越來越難管理,然而 fetch 回傳的是一個 Promise,我們也就能直接利用 await 或 promise.all 的作法,輕鬆掌握同步與非同步之間的轉換。

下方的例子是一個非同步的程式碼,因為沒有進行任何的同步處理,所以執行之後,會「先出現 hello 的文字,接著才是透過 fetch 得到的結果。

const postURL = (name,age) => {
    const uri = 'https://script.google.com/macros/s/AKfycbxXH6aPsldTBeS41WRMnJEA5Xstc7cYMj6YimDO2Al7H6DkJZiz/exec';
    return fetch(uri, {
        method:'POST',
        body:encodeURI(JSON.stringify({
            name:name,
            age:age
        })),
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
        }
    })
    .then(res => {
        return res.json();
    }).then(result =>{
        console.log(result);
    });
};

postURL('oxxo',18);
console.log('hello!!!');
postURL('tom',18);

因為 fetch 的特性,可以改成 async 和 await 的寫法,執行後也就能按照我們要的順序進行。

~async function(){  // 開頭設定為 async
    const postURL = (name,age) => {
        const uri = 'https://script.google.com/macros/s/AKfycbxXH6aPsldTBeS41WRMnJEA5Xstc7cYMj6YimDO2Al7H6DkJZiz/exec';
        return fetch(uri, {
            method:'POST',
            body:encodeURI(JSON.stringify({
            name:name,
            age:age
            })),
            headers: {
            'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
            }
        })
        .then(res => {
            return res.json();
        }).then(result =>{
            console.log(result);
        });
    };

    await postURL('oxxo',18); // 使用 await
    console.log('hello!!!');
    await postURL('tom',18);  // 使用 await
}();

最後那段 await 的程式碼,也可以改成 promise.all 的作法,就會先 fetch,然後再出現 hello 的文字,不過也因為 promise.all 無法保證其載入順序,就可能會發生 tom 在 oxxo 之前出現的狀況呦。

await Promise.all([postURL('oxxo',18), postURL('tom',18)]);
console.log('hello!!!');

小結

Fetch API 的神奇,簡化了許多原本較為複雜的用法,也讓程式碼寫起來更加乾淨易讀好維護,我的 Blog 現在也是使用 Fetch 呦~ 推薦給大家~~

更多參考資源:

備註

上面的範例我都是使用 Google Apps Script 做簡易後端,但在接收 Fetch POST 方法松出的資料時,接收的內容卻跟以往不太相同,因此在 Apps Script 的取值要改成下面的寫法,出來的結果才會是正常的~

詳細介紹請參考我之前的文章:簡易後端實作 ( Google Apps Script )

function doPost(e) {
    var param = JSON.parse(Object.keys(e.parameter)[0]); // 這段是重點!!!!
    var name = param.name;
    var age = param.age;
    var replyMsg = {
        name: name,
        age: age,
        text: '你的名字是 '+name+',年紀 '+age+' 歲~'
    };    
    var JSONString = JSON.stringify(replyMsg);
    return ContentService.createTextOutput(JSONString).setMimeType(ContentService.MimeType.JSON);
}

有興趣瞧瞧其他新文章嗎?