Fetch API 已經出現好久了,不少公司和我的都在鼓吹 Fetch 多麼牛逼,這點必需要贊成。javascript
Fetch 使用來替代老掉牙的 XMLHttpRequest,XMLHttpRequest 在設計上有着不少缺陷,好比調用方式混亂,不注重分離設計的原則等等,因此後來纔會有了相似 JQuery Ajax 之類的庫出現。前端
首先先給出一個明確的觀點,我不否定 Fetch 相反我認爲是很優秀的,可是 Fetch API 總體用起來仍是有一些不爽的,雖然得益於 Promise 的助攻,可是更多的缺陷也來自 Promise,因此本文就針對基於標準 Promise 實現的 Fetch 吐槽一下用起來的不爽。java
Promise 中文翻譯「承諾」,在異步世界裏真的沒有什麼比承若更加劇要了,由於真的不知道下一個出現的會是誰。git
Promise 中值分紅如今值和未來值兩個部分,未來值正是咱們所關心的,因此給 Promise 下一個簡單定義就是:獲取意料當中值。github
在 Promise 中分紅三個狀態:json
是的就是隻有三種狀態(坑點就在這裏)後端
只提供了最簡單的 API:api
在 Promise 原型鏈上有兩種方法:promise
因此 Promise 總的來講只有三種狀態,四個方法、兩個原型方法,多麼簡單。瀏覽器
以上內容來自 MDN Promise
沒有這個功能確實很蛋疼,當遇到網絡不暢的時候,不能總是等待吧,這樣太噁心了。
這個槽點仍是在 Promise 自己上,因爲只有三種狀態,成功、掛起、失敗,並無取消啊,WTF???黑人問號??
怎麼辦?彈藥不夠敵人來造,最大的敵人就是 Promise 自己。Promise 中有一個方法叫作 race,該方法一組 Promise 中只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行後面的處理。
So~,有了這種機制就能夠造一個假的 Timeout 出來了。
function hackFetch(url, timeout=10, params={}) {
// 用 Promise 包裝一個 timeout 的 reject
var _abort = new Promise((resolve, reject) => {
setTimeout(() => reject('abort promise'), timeout);
})
var _fetch = fetch(url, params);
return Promise.race([_fetch, _abort])
}複製代碼
實現的代碼很簡單,兩個 Promise,一個是 timeout 、一個是 Fetch,對這樣就完成了。
而後再來一個工廠方法,多建立幾個,來嘗試一下。
// hackFetch 的工廠方法
function createHackFetch(url, timeout=10, params={}) {
return () => {
return hackFetch(url, timeout, params)
.then(res => res.json())
.then(json => textDOM.value += json.message + '\n')
.catch(err => alert('fetch 超時'))
}
}複製代碼
實驗使用的是 Express,實現了 4 個接口,分別 0,5,10,15 秒返回數據。
完整例子能夠轉到該項目的 Repo
當我點擊「測試15秒 timeout 的 fetch」事後的 10 秒,出現了 alert,中斷了此次 hackPromise,沒有在下面的 textarea 中添加獲取到字符串。
事情並不會那麼美好,確認完成這個 alert 之後。觀察那個 fifteen-delay
的請求,它依然返回數據。
此坑開始在於 Promise 自己沒有 cancel 機制。經過 hack 出來的帶有 Timeout 機制的 Fetch,只不過的騙過了本身,可是沒有騙過了瀏覽器。
這種方法是很危險的行爲。輕的來看結果是顯示和實際狀況不一致罷了,可是嚴重的來看,本不該該出現東西卻出現了,確確實實是一個漏洞。
此法有解嗎?目前來看前端無解,後端能夠經過設置鏈接的 Timeout 時間來解決這個問題,Nginx 能夠經過設置 send_timeout
來規定 Timeout 時間。
在 MDN 的 Using Fetch 中有那麼一段話:
The Promise returned from
fetch()
won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (withok
status set to false), and it will only reject on network failure or if anything prevented the request from completing.
翻譯過來就是:
從 fetch()
返回的 Promise 將不會拒絕HTTP錯誤狀態, 即便響應是一個 HTTP 404 或 500。相反,它會正常解決 (其中ok狀態設置爲false), 只有在網絡故障時或者請求被阻止時,它纔會拒絕。
這個其實這個相比上一個來講並非什麼嚴重的坑,只不過在開發上變的更加繁瑣一些,這個偏偏又和 Fetch 的理念相悖。
app.get('/api/error-five-delay', function(req, res) {
res.type('json');
res.status(500)
setTimeout(() => {
res.send(JSON.stringify({
message: 'there is a error response'
}));
}, 5000)
});複製代碼
添加一個 5 秒後返回 500 錯誤的接口,使用一個建立正常 Fetch 的工廠方法,綁定到 button 上。
function createFetch(url, params={}) {
return () => {
return fetch(url, params)
.then(res => res.json())
.then(json => textDOM.value += json.message + '\n')
.catch(err => alert('請求失敗'))
}
}
// 綁定事件
document.getElementById('error-five-fetch').onclick = createFetch('/api/error-five-delay');複製代碼
5 秒以後,message 信息如願的被添加到了 textarea 上。此時瀏覽器作到了它職責在控制檯中給出了錯誤,可是 Promise 忽略了它。
因此如 MDN 中所言,咱們必須手動的檢查 response 中 ok 屬性是否爲 false
,好了要在造一個假的 Fetch 了。
function xfetch(url, params) {
return fetch(url, params)
// 處理錯誤時候的 json
.then(res => res.json().then(json => res.ok ? json : Promise.reject(json)))
.then(json => textDOM.value += json.message + '\n')
.catch(err => alert(err.message))
}
function createXfetch(url, params={}) {
return () => xfetch(url, params)
}
document.getElementById('error-handling-five-fetch').onclick = createXfetch('/api/error-five-delay');複製代碼
網絡上有不少這樣處理髮生錯誤時候的 json,各類各樣的方法都有,其中同樣的就是必須先把 json 從 Promise 從解析出來,而後再來處理 response.ok 的狀態。
完整例子能夠轉到該項目的 Repo
其實這麼作面對 json 數據時候沒有壓力,可是對於須要解析多種數據時候還須要更多的參數和封裝,好比數據來源是 xml 或者 plain。
好嘛,又違背了 Promise 的設計原則。
文中沒有實現一個 timeout 和 錯誤 json 處理例子,其實把 timeout 版中替換成 xfetch 就行了。
自從 Promise 的出現,在編寫異步任務上有了很大的改進,Fetch 也孕育而生,在使用 Fetch 帶來的簡單、高效的同時也要主要它的坑點所在。本文只是總結了很小的一部分,在 Promise 還有無數的坑等着別去跳。
async / await 確定是下一個方向,在還沒完善以前,爲了新老語法過渡使用 Promise 無疑是很是聰明的選擇。能夠給老代碼以接口的方式打上一個 polyfill,同時新語法兼容 Promise 。這樣完美的避開了像 Python 青黃不接的尷尬局面,Python 要加油了。
我是一個 Python 工程師,Python 大發好啊,Python 大發好啊,Python 大發好啊。