一起來把煩人 XMLHttpRequest 變成 Fetch 怎麼樣?

首先文章開頭我們先來說明一下 XMLHttpRequest 為什麼被稱為「煩人」的原因,然後說明對應且簡潔的Fetch API。

本篇文章只是模擬筆者常用的 Fetch 方法,如果要做到 fetch 的 polyfill ,建議是使用經過測試、千錘百煉的專案 (eg. github/fetch)。

(以下我們簡稱 XHR

XHR 對於開發者來說相當陌生,大概十年前的人都覺得操作 XHR 很煩,否則怎麼會有 jQuery 的 ajax 誕生呢?

Photo by Aarón Blanco Tejedor on Unsplash

話不多說,來看看最簡單的 XHR 範例(MDN):

function reqListener () {
console.log(this.responseText);
}

var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.example.org/example.txt");
oReq.send();
  1. 創建 XMLHttpRequest 的實例 — oReq
  2. open 設定「請求方法」以及「目標」
  3. 掛載 load callback
  4. 使用 send 方法發起請求

請求回來後,依照 load 的回調來決定怎麼處理資料,挺饒舌的。

fetch("http://www.example.org/example.txt")
.then(res=>res.text())
.then(console.log)

其實有點作弊,因為 fetch 如果不指定方法的話會使用 GET 。

還可以更作弊嗎?由於是回傳 Promise ,可以用 ES7 的 await 做的更好懂:

let res = await fetch('http://www.example.org/example.txt"');
let text = await res.text();
console.log(text);
const fetches = target => 
new Promise((resolve,reject)=>{
const XHR = new XMLHttpRequest();
XHR.open('GET',target);
XHR.addEventListener('load',()=> resolve(new Response(XHR.response)));
XHR.send();
})

由於 fetch 後的第一個 then 拿到的是 Response實體,使用我們必須在 resolve 內傳入。

我們多加入一個監聽器,來監聽 error 事件是否發生。

  XHR.addEventListener('error', reject)

初步的 fetch 就快實現了。

fetch(url, {
body: JSON.stringify(data),
headers: {
'user-agent': 'Mozilla/4.0 MDN Example',
'content-type': 'application/json'
},
method: 'POST',
})

我們可以知道一件事 fetch 接受兩個參數,這邊稱為 args[0]args[1] 好了。

  1. 目標是 args[0]
  2. 如果 args[1] 不存在 → GET 請求
  3. 如果 args[1] 存在,且args[1].method 存在 → 有指定 method
  4. 如果 args[1] 存在,且args[1].headers 存在 → 存在headers
  5. 如果 args[1] 存在,且args[1].body 存在 → 存在body

我們就一一塞進去吧!

XHR.setRequestHeader(key, value)

假設 args[1].headers 存在,我們也要用一樣的方式,把該 object 的 key 與對應的 value 塞進去。

首先我們把 args 裡頭的資訊,包括目標 URL、method、Headers、body,抽取出來,並且丟進我們設計好的 Promise XHR

第五行所做的事就是依照 Header 物件的 Key 遍尋所有對應的 value,並且依照 setRequestHeader 塞進去。

大功告成。

我們來透過開放跨域請求的https://httpbin.org/ 來測試吧!由於 Medium 有設定 CSP (Content Security Policy) ,所以可以的話其他分頁試試看吧!

不提供 args[1] 的 fetch

fetches('https://httpbin.org/get')
.then(res=>res.text())
.then(console.log)

提供 args[1] 的 GET (with headers)

fetches('https://httpbin.org/get',{
method:'GET',
headers: {
'author': '@realdennis'
}
})
.then(res=>res.text())
.then(console.log)

POST (with body)

fetches('https://httpbin.org/post',{
method:'POST',
body:JSON.stringify({whoami:'realdennis'}),
})
.then(res=>res.text())
.then(console.log)
Photo by Priscilla Du Preez on Unsplash

If any interest, 👉 https://realdennis.me.

If any interest, 👉 https://realdennis.me.