前端請求的那些事兒
作者:Jerry
引言
前端是一個快速發展的領域,而在前端的技術棧當中,前端請求又是最見的一個領域,通過請求介面資料,才能將一個靜態的頁面動態化。本文將以前端發展的時間軸來逐一分析前端請求的技術演變及其優劣,針對這一課題,作者查閱了相關資料加以自己的理解,如有錯誤,煩請指出。
XMLHttpRequest
XMLHttpRequest是最早出現的與伺服器交換資料的方案,有了XMLHttpRequest,開發者終於可以在不重新載入頁面的情況下更新網頁,可以在頁面載入後請求接受以及傳送資料。而所有瀏覽器均可以獲取XMLHttpRequest物件:
var xhr = new XMLHttpRequest(); //獲取xhr物件
但是XMLHttpRequest是個比較粗燥的底層物件,各個瀏覽器對其的建立方法也不同,以下是相容方法:
var xhr; if (window.XMLHttpRequest) { // Mozilla, Safari... xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); //IE5,6 } catch (e) {} } }
使用XMLHttpRequest發起一個get請求
//get請求 xhr.open("GET","test1.txt",true); xhr.send();
完整的post請求程式碼如下:
var xhr; if (window.XMLHttpRequest) { // Mozilla, Safari... xhr = new XMLHttpRequest(); } else if (window.ActiveXObject) { // IE try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {} } } if (xhr) { xhr.onreadystatechange = onReadyStateChange; xhr.open('POST', '/api', true); // 設定 Content-Type 為 application/x-www-form-urlencoded // 以表單的形式傳遞資料 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('username=admin&password=root'); } // onreadystatechange 方法 function onReadyStateChange() { // 該函式會被呼叫四次 if (xhr.readyState === 4 &&xhr.status === 200) { console.log('執行成功'); } else { console.log('執行出錯'); } }
Jquery Ajax
說到Jquery,這是一個時代,幾乎統治了前端10年有餘,徹底解決了UI層與資料層互動的問題,直到三大框架(Angular/React/Vue)的出現,前端進入MVVM浪潮。而Ajax將XHR進行封裝,讓開發者可以更加便捷方便進行使用。
$.ajax({ //標準寫法 type: 'POST', url: url, data: data, dataType: dataType, success: function () {}, error: function () {} }); $.get(url,function(){}); //get請求 $.post(url,body,function(){}); //post請求 $.getJSON(url,function(){}); //get請求從伺服器載入Json編碼
優點:
- 對原生XHR的封裝
- 針對MVC的程式設計
- 完美的相容性
- 支援jsonp
缺點:
- 不符合MVVM
- 非同步模型不夠現代,不支援鏈式,程式碼可讀性差
- 整個Jquery太大,引入成本過高
Fetch
fetch其實是一個新世界,脫離的XHR,完全是基於Promise的非同步處理機制,使用起來會比起ajax更加簡單。
使用fetch的程式碼會相比xhr來說更具有條理性
fetch(url).then(function(response) { return response.json(); }).then(function(data) { console.log(data); }).catch(function(e) { console.log("Oops, error"); });
在使用ES6的箭頭函式後
fetch(url).then(response => response.json()) .then(data => console.log(data)) .catch(e => console.log("Oops, error", e))
優點:
看到以上,或許你會覺得fetch真的很美好,但是請了解,fetch本身是一個 low-level 的 API,它註定不會像你習慣的 $.ajax 或是 axios 等庫幫你封裝各種各樣的功能或實現。
所以它是存在一定的缺點:
- 相容性比較悽慘,低級別瀏覽器均不支援,需要實現fetch的polyfill了。思路其實很簡單,就是判斷瀏覽器是否支援原生的fetch,不支援的話,就仍然使用XMLHttpRequest的方式實現,同時結合Promise來進行封裝。常見的polyfill就有:es6-promise,babel-polyfill,fetch-ie8等

- 不支援jsonp,可以引入fetch-jsonp
//安裝 npm install fetch-jsonp --save-dev //使用 fetchJsonp(url, { timeout: 3000, jsonpCallback: 'callback' }).then(function(response) { console.log(response.json()); }).catch(function(e) { console.log(e) });
- 沒有攔截器,需要額外再封裝一層或者fetch-interceptor
- 預設不帶cookie,需要新增配置
fetch(url,{ credentials: 'include' //include表示cookie既可同域,也可跨域,‘same-origin’表示只可同域 });
- 沒有abort,不支援timeout超時處理
可以用Promise.race()實現,Promise.race(iterable) 方法返回一個Promise物件,只要 iterable 中任意一個Promise 被 resolve 或者 reject 後,外部的Promise 就會以相同的值被 resolve 或者 reject。
- 無法獲取progress狀態
fetch中的Response.body 中實現了getReader()方法用於讀取原始位元組流, 該位元組流可以迴圈讀取。參考javascript - Progress indicators for fetch? - Stack Overflow2016 - the year of web streams
Axios
axios也是比較新的網路請求的類庫,並且被尤雨溪尤大推薦,已成為VUE的網路請求標配,也是十分的火爆。它本身也是對原生XHR的封裝。
- 支援node,建立http請求
- 支援Promise API
- 客戶端防止CSRF:每個請求帶一個cookie拿到的key
- 攔截請求和響應
- 可取消請求
相容性上雖然axios本質上也是對原生XHR的封裝,但是它也依賴原生ES6 Promise的實現,和fetch一樣需要polyfill的相容。

安裝
//npm npm install axios //cdn <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
基本使用如下:
axios({ method: 'GET', url: url, }) .then(res => {console.log(res)}) .catch(err => {console.log(err)}) // get請求 axios.get(url) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); // post請求 axios.post(‘/user’, { name: 'Jerry', lastName: 'Liang' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
特殊場景的處理
在開發過程中,經常會遇到比較尷尬的場景就是多請求的序列與併發,併發比較容易解決,不存在回撥地獄,但是程式碼可讀性就會容易變得很渣,而序列問題對於前端是絕望的,最好的辦法是後端來做合併,如果後端不做這塊的處理,前端就必須來面對回撥地獄。
多請求序列
// ajax $.ajax({ url: '', data: '', success: function (data) { $.ajax({ url: '', data: '', success: function (data) { $.ajax({ // 如此一層巢狀一層 }) } }) } }) //axios axios.get(url) .then(res => { return axios.get(url,{ {name:result.name} }); }).then(res => { //如此一層層巢狀 });
多請求並行
//ajax 通過計數器實現(雖然Jquery支援$.when的方式,但此處不做案例) var num = 0; function all(){ num++; if(n>=3)console.log('三個請求全部完成'); } $.ajax({ url: '', data: '', success: function (data) { console.log("ajax請求1 完成"); all(); } }) $.ajax({ url: '', data: '', success: function (data) { console.log("ajax請求2 完成"); all(); } }) $.ajax({ url: '', data: '', success: function (data) { console.log("ajax請求3 完成"); all(); } }) //axios function getInfo() { return axios.get(url); } function getUser() { return axios.get(url); } axios.all([getInfo(), getUser()]) .then(axios.spread(function (info, user) { // 兩個請求現在都執行完成 }));
如何選擇(個人理解,僅供參考)
- 首先可以肯定的是,如果你的程式碼依舊是基於Jquery,那毫無疑問,ajax就是你最好的選擇。
- 如果你使用的是任意MVVM框架,建議無腦使用axios,fetch在實際專案使用中,需要各種的封裝和異常處理,並非開箱即用,而axios可以做到直接替換$.ajax。
- 如果就是要使用fetch,那相信你也一定能封裝成自己的一套最佳實踐。