https://segmentfault.com/a/1190000003810652javascript
原諒我作一次標題黨,Ajax 不會死,傳統 Ajax 指的是 XMLHttpRequest(XHR),將來如今已被 Fetch 替代。php
最近把阿里一個千萬級 PV 的數據產品所有由 jQuery 的 $.ajax
遷移到 Fetch
,上線一個多月以來運行很是穩定。結果證實,對於 IE8+ 以上瀏覽器,在生產環境使用 Fetch 是可行的。html
因爲 Fetch API 是基於 Promise 設計,有必要先學習一下 Promise,推薦閱讀 MDN Promise 教程。舊瀏覽器不支持 Promise,須要使用 polyfill es6-promise 。java
本文不是 Fetch API 科普貼,實際上是講異步處理和 Promise 的。Fetch API 很簡單,看文檔很快就學會了。推薦 MDN Fetch 教程 和 萬能的WHATWG Fetch 規範node
XMLHttpRequest 是一個設計粗糙的 API,不符合關注分離(Separation of Concerns)的原則,配置和調用方式很是混亂,並且基於事件的異步模型寫起來也沒有現代的 Promise,generator/yield,async/await 友好。jquery
Fetch 的出現就是爲了解決 XHR 的問題,拿例子說明:git
使用 XHR 發送一個 json 請求通常是這樣:es6
var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'json'; xhr.onload = function() { console.log(xhr.response); }; xhr.onerror = function() { console.log("Oops, error"); }; xhr.send();
使用 Fetch 後,頓時看起來好一點github
fetch(url).then(function(response) { return response.json(); }).then(function(data) { console.log(data); }).catch(function(e) { console.log("Oops, error"); });
使用 ES6 的 箭頭函數 後:web
fetch(url).then(response => response.json()) .then(data => console.log(data)) .catch(e => console.log("Oops, error", e))
如今看起來好不少了,但這種 Promise 的寫法仍是有 Callback 的影子,並且 promise 使用 catch 方法來進行錯誤處理的方式有點奇怪。不用急,下面使用 async/await 來作最終優化:
注:async/await 是很是新的 API,屬於 ES7,目前尚在 Stage 1(提議) 階段,這是它的完整規範。使用 Babel 開啓 runtime 模式後能夠把 async/await 無痛編譯成 ES5 代碼。也能夠直接使用 regenerator 來編譯到 ES5。
try { let response = await fetch(url); let data = response.json(); console.log(data); } catch(e) { console.log("Oops, error", e); } // 注:這段代碼若是想運行,外面須要包一個 async function
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 優勢主要有:
語法簡潔,更加語義化
基於標準 Promise 實現,支持 async/await
同構方便,使用 isomorphic-fetch
下面是重點↓↓↓
先看一下 Fetch 原生支持率:
原生支持率並不高,幸運的是,引入下面這些 polyfill 後能夠完美支持 IE8+ :
因爲 IE8 是 ES3,須要引入 ES5 的 polyfill: es5-shim, es5-sham
引入 Promise 的 polyfill: es6-promise
引入 fetch 探測庫:fetch-detector
引入 fetch 的 polyfill: fetch-ie8
可選:若是你還使用了 jsonp,引入 fetch-jsonp
可選:開啓 Babel 的 runtime 模式,如今就使用 async/await
Fetch polyfill 的基本原理是探測是否存在 window.fetch
方法,若是沒有則用 XHR 實現。這也是 github/fetch 的作法,可是有些瀏覽器(Chrome 45)原生支持 Fetch,但響應中有中文時會亂碼,老外又不太關心這種問題,因此我本身才封裝了 fetch-detector
和 fetch-ie8
只在瀏覽器穩定支持 Fetch 狀況下才使用原生 Fetch。這些庫如今天天有幾千萬個請求都在使用,絕對靠譜!
終於,引用了這一堆 polyfill 後,能夠愉快地使用 Fetch 了。但要當心,下面有坑:
Fetch 請求默認是不帶 cookie 的,須要設置 fetch(url, {credentials: 'include'})
服務器返回 400,500 錯誤碼時並不會 reject,只有網絡錯誤這些致使請求不能完成時,fetch 纔會被 reject。
居然沒有提到 IE,這實在太不科學了,如今來詳細說下 IE
全部版本的 IE 均不支持原生 Fetch,fetch-ie8 會自動使用 XHR 作 polyfill。但在跨域時有個問題須要處理。
IE8, 9 的 XHR 不支持 CORS 跨域,雖然提供 XDomainRequest
,但這個東西就是玩具,不支持傳 Cookie!若是接口須要權限驗證,仍是乖乖地使用 jsonp 吧,推薦使用 fetch-jsonp。若是有問題直接提 issue,我會第一時間解決。
因爲 Fetch 是典型的異步場景,因此大部分遇到的問題不是 Fetch 的,實際上是 Promise 的。ES6 的 Promise 是基於 Promises/A+ 標準,爲了保持簡單簡潔,只提供極簡的幾個 API。若是你用過一些牛 X 的異步庫,如 jQuery(不要笑) 、Q.js 或者 RSVP.js,可能會感受 Promise 功能太少了。
Deferred 能夠在建立 Promise 時能夠減小一層嵌套,還有就是跨方法使用時很方便。
ECMAScript 11 年就有過 Deferred 提案,但後來沒被接受。其實用 Promise 不到十行代碼就能實現 Deferred:es6-deferred。如今有了 async/await,generator/yield 後,deferred 就沒有使用價值了。
標準 Promise 沒有提供獲取當前狀態 rejected 或者 resolved 的方法。只容許外部傳入成功或失敗後的回調。我認爲這實際上是優勢,這是一種聲明式的接口,更簡單。
always 能夠經過在 then 和 catch 裏重複調用方法實現。finally 也相似。progress 這種進度通知的功能尚未用過,暫不知道如何替代。
Fetch 替換 XHR 只是時間問題,如今看到國外不少新的庫都默認使用了 Fetch。
最後再作一個大膽預測:因爲 async/await 這類新異步語法的出現,第三方的 Promise 類庫會逐漸被標準 Promise 替代,使用 polyfill 是如今比較明智的作法。
轉至個人博客,原文地址:https://github.com/camsong/blog/issues/2
想不想加入阿里巴巴一塊兒玩 ES7,React,FRP 等最新技術,歡迎簡歷到 neosoyn@gmail.com