[轉載]傳統 Ajax 已死,Fetch 永生

最近由於公司項目比較緊,事情比較多,所以停了很長一段時間沒有寫文章了,在這裡給大家道個歉,後面我會盡量克服,堅持給大家分享一些個人在工作和學習中的一些心得體會以及轉載一些好的文章。

原諒我做一次標題黨,Ajax 不會死,傳統 Ajax 指的是 XMLHttpRequest(XHR),未來現在已被 Fetch 替代。

最近把阿里一個千萬級 PV 的數據產品全部由 jQuery 的 $.ajax 遷移到 Fetch,上線一個多月以來運行非常穩定。結果證明,對於 IE8+ 以上瀏覽器,在生產環境使用 Fetch 是可行的。

由於 Fetch API 是基於 Promise 設計,有必要先學習一下 Promise,推薦閱讀 MDN Promise 教程。舊瀏覽器不支持 Promise,需要使用 polyfill es6-promise 。

本文不是 Fetch API 科普貼,其實是講非同步處理和 Promise 的。Fetch API 很簡單,看文檔很快就學會了。推薦 MDN Fetch 教程) 和 萬能的WHATWG Fetch 規範

Why Fetch

XMLHttpRequest 是一個設計粗糙的 API,不符合關注分離(Separation of Concerns)的原則,配置和調用方式非常混亂,而且基於事件的非同步模型寫起來也沒有現代的 Promise,generator/yield,async/await 友好。

Fetch 的出現就是為了解決 XHR 的問題,拿例子說明:

使用 XHR 發送一個 json 請求一般是這樣:

var xhr = new XMLHttpRequest();nnxhr.open(GET, url);nnxhr.responseType = json;nnnnxhr.onload = function() {nn console.log(xhr.response);nn};nnnnxhr.onerror = function() {nn console.log("Oops, error");nn};nnnnxhr.send();n

使用 Fetch 後,頓時看起來好一點

fetch(url).then(function(response) {nn return response.json();nn}).then(function(data) {nn console.log(data);nn}).catch(function(e) {nn console.log("Oops, error");nn});n

使用 ES6 的 箭頭函數 後:

fetch(url).then(response => response.json())nn .then(data => console.log(data))nn .catch(e => console.log("Oops, error", e))n

現在看起來好很多了,但這種 Promise 的寫法還是有 Callback 的影子,而且 promise 使用 catch 方法來進行錯誤處理的方式有點奇怪。不用急,下面使用 async/await 來做最終優化:

註:async/await 是非常新的 API,屬於 ES7,目前尚在 Stage 1(提議) 階段,這是它的完整規範。使用 Babel 開啟 runtime 模式後可以把 async/await 無痛編譯成 ES5 代碼。也可以直接使用regenerator 來編譯到 ES5。

try {nn let response = await fetch(url);nn let data = await response.json();nn console.log(data);nn} catch(e) {nn console.log("Oops, error", e);nn}nn// 註:這段代碼如果想運行,外面需要包一個 async functionn

duang~~ 的一聲,使用 await 後,寫非同步代碼就像寫同步代碼一樣爽。await 後面可以跟 Promise 對象,表示等待 Promise resolve() 才會繼續向下執行,如果 Promise 被 reject() 或拋出異常則會被外面的 try...catch 捕獲。

Promise,generator/yield,await/async 都是現在和未來 JS 解決非同步的標準做法,可以完美搭配使用。這也是使用標準 Promise 一大好處。最近也把項目中使用第三方 Promise 庫的代碼全部轉成標準 Promise,為以後全面使用 async/await 做準備。

另外,Fetch 也很適合做現在流行的同構應用,有人基於 Fetch 的語法,在 Node 端基於 http 庫實現了node-fetch,又有人封裝了用於同構應用的 isomorphic-fetch。

註:同構(isomorphic/universal)就是使前後端運行同一套代碼的意思,後端一般是指 NodeJS 環境。

總結一下,Fetch 優點主要有:

  1. 語法簡潔,更加語義化

  2. 基於標準 Promise 實現,支持 async/await

  3. 同構方便,使用 isomorphic-fetch

Fetch 啟用方法

下面是重點↓↓↓

先看一下 Fetch 原生支持率:

原生支持率並不高,幸運的是,引入下面這些 polyfill 後可以完美支持 IE8+ :

  1. 由於 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham

  2. 引入 Promise 的 polyfill: es6-promise

  3. 引入 fetch 探測庫:fetch-detector

  4. 引入 fetch 的 polyfill: fetch-ie8

  5. 可選:如果你還使用了 jsonp,引入 fetch-jsonp

  6. 可選:開啟 Babel 的 runtime 模式,現在就使用 async/await

Fetch polyfill 的基本原理是探測是否存在 window.fetch 方法,如果沒有則用 XHR 實現。這也是github/fetch 的做法,但是有些瀏覽器(Chrome 45)原生支持 Fetch,但響應中有中文時會亂碼,老外又不太關心這種問題,所以我自己才封裝了 fetch-detector 和 fetch-ie8 只在瀏覽器穩定支持 Fetch 情況下才使用原生 Fetch。這些庫現在 每天有幾千萬個請求都在使用,絕對靠譜

終於,引用了這一堆 polyfill 後,可以愉快地使用 Fetch 了。但要小心,下面有坑:

Fetch 常見坑

  • Fetch 請求默認是不帶 cookie 的,需要設置 fetch(url, {credentials: include})

  • 伺服器返回 400,500 錯誤碼時並不會 reject,只有網路錯誤這些導致請求不能完成時,fetch 才會被 reject。

竟然沒有提到 IE,這實在太不科學了,現在來詳細說下 IE

IE 使用策略

所有版本的 IE 均不支持原生 Fetch,fetch-ie8 會自動使用 XHR 做 polyfill。但在跨域時有個問題需要處理。

IE8, 9 的 XHR 不支持 CORS 跨域,雖然提供 XDomainRequest,但這個東西就是玩具,不支持傳 Cookie!如果介面需要許可權驗證,還是乖乖地使用 jsonp 吧,推薦使用 fetch-jsonp。如果有問題直接提 issue,我會第一時間解決。

Fetch 和標準 Promise 的不足

由於 Fetch 是典型的非同步場景,所以大部分遇到的問題不是 Fetch 的,其實是 Promise 的。ES6 的 Promise 是基於 Promises/A+ 標準,為了保持 簡單簡潔 ,只提供極簡的幾個 API。如果你用過一些牛 X 的非同步庫,如 jQuery(不要笑) 、Q.js 或者 RSVP.js,可能會感覺 Promise 功能太少了。

沒有 Deferred

Deferred 可以在創建 Promise 時可以減少一層嵌套,還有就是跨方法使用時很方便。

ECMAScript 11 年就有過 Deferred 提案,但後來沒被接受。其實用 Promise 不到十行代碼就能實現 Deferred:es6-deferred。現在有了 async/await,generator/yield 後,deferred 就沒有使用價值了。

沒有獲取狀態方法:isRejected,isResolved

標準 Promise 沒有提供獲取當前狀態 rejected 或者 resolved 的方法。只允許外部傳入成功或失敗後的回調。我認為這其實是優點,這是一種聲明式的介面,更簡單。

缺少其它一些方法:always,progress,finally

always 可以通過在 then 和 catch 里重複調用方法實現。finally 也類似。progress 這種進度通知的功能還沒有用過,暫不知道如何替代。

不能中斷,沒有 abort、terminate、onTimeout 或 cancel 方法

Fetch 和 Promise 一樣,一旦發起,不能中斷,也不會超時,只能等待被 resolve 或 reject。幸運的是,whatwg 目前正在嘗試解決這個問題 whatwg/fetch#27

資料

  • WHATWG Fetch 規範

  • Fetch API 簡介

  • 教你馴服 async

  • 阮一峰介紹 async

最後

Fetch 替換 XHR 只是時間問題,現在看到國外很多新的庫都默認使用了 Fetch。

最後再做一個大膽預測:由於 async/await 這類新非同步語法的出現,第三方的 Promise 類庫會逐漸被標準 Promise 替代,使用 polyfill 是現在比較明智的做法。

原文地址:github.com/camsong/blog

本文同步發佈於微信公眾號:WeCode365 有興趣的同學可以關注一下。


推薦閱讀:

jquery ajax怎麼通過header傳遞參數? 不想通過url傳參!
115登錄頁的long polling在chrome裡面為什麼看不到返回結果?
怎樣合理地使用 Ajax ?過度使用 Ajax 會有哪些弊端?
Web 前端工程師需要 AJAX?感覺請求提交都是後端的事情。
js 詞法作用域(靜態) 與 作用域鏈 指向 活動對象(動態--執行) 不矛盾嗎?

TAG:Ajax | 前端入门 | 自学编程 |