摘要:是在收到響應后執(zhí)行的函數(shù),可以不用返回。一步步介紹了如何構建以及使用中間層,來統(tǒng)一管理接口地址,最后還介紹了下中間件等高級功能。
零、問題的由來
開門見山地說,這篇文章是一篇安利軟文~,安利的對象就是最近搞的 tua-api。
顧名思義,這就是一款輔助獲取接口數(shù)據(jù)的工具。
發(fā)請求相關的工具辣么多,那我為啥要用你呢?
理想狀態(tài)下,項目中應該有一個 api 中間層。各種接口在這里定義,業(yè)務側不應該手動編寫接口地址,而應該調用接口層導出的函數(shù)。
import { fooApi } from "@/apis/" fooApi .bar({ a: "1", b: "2" }) // 發(fā)起請求,a、b 是請求參數(shù) .then(console.log) // 收到響應 .catch(console.error) // 處理錯誤
那么如何組織實現(xiàn)這個 api 中間層呢?這里涉及兩方面:
如何發(fā)請求,即“武器”部分
如何組織管理 api 地址
讓我們先回顧一下有關發(fā)請求的歷史。
一、如何發(fā)請求 1.1.原生 XHR (XMLHttpRequest)說到發(fā)請求,最經(jīng)典的方式莫過于調用瀏覽器原生的 XHR。在此不贅述,有興趣可以看看MDN 上的文檔。
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() // 在萬惡的 IE 上可能還沒有 XMLHttpRequest 這對象 : new ActiveXObject("Microsoft.XMLHTTP") xhr.open("GET", "some url") xhr.responseType = "json" // 傳統(tǒng)使用 onreadystatechange xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText) } } // 或者直接使用 onload 事件 xhr.onload = function () { console.log(xhr.response) } // 處理出錯 xhr.onerror = console.error xhr.send()
這代碼都不用看,想想就頭皮發(fā)麻...
1.2.jQuery 封裝的 ajax由于原生 XHR 寫起來太繁瑣,再加上當時 jQuery 如日中天。日常開發(fā)中用的比較多的還是 jQuery 提供的 ajax 方法。jQuery ajax 文檔點這里
var params = { url: "some url", data: { name: "Steve", location: "Beijing" }, } $.ajax(params) .done(console.log) .fail(console.error)
jQuery 不僅封裝了 XHR,還十分貼心地提供跨域的 jsonp 功能。
$.ajax({ url: "some url", data: { name: "Steve", location: "Beijing" }, dataType: "jsonp", success: console.log, error: console.error, })
講道理,jQuery 的 ajax 已經(jīng)很好用了。然而隨著 Vue、React、Angular 的興起,連 jQuery 本身都被革命了。新項目為了發(fā)個請求還引入巨大的 jQuery 肯定不合理,當然后面這些替代方案也功不可沒...
1.3.現(xiàn)代瀏覽器的原生 fetchXHR 是一個設計粗糙的 API。記得當年筆試某部門的實習生的時候就有手寫 XHR 的題目,我反正記不住 api,并沒有寫出來...
fetch api 基于 Promise 設計,調用起來比 XHR 方便多了。
fetch(url) .then(res => res.json()) .then(console.log) .catch(console.error)
async/await 自然也能使用
try { const data = await fetch(url).then(res => res.json()) console.log(data) } catch (e) { console.error(e) }
當然 fetch 也有不少的問題
兼容性問題
使用繁瑣,詳見參考文獻之 fetch 沒有你想象的那么美
不支持 jsonp(雖然理論上不應該支持,但實際上日常還是需要使用的)
只對網(wǎng)絡請求報錯,對400,500都當做成功的請求,需要二次封裝
默認不會帶 cookie,需要添加配置項
不支持 abort,不支持超時控制,使用 setTimeout 及 Promise.race 的實現(xiàn)的超時控制并不能阻止請求過程繼續(xù)在后臺運行,造成了流量的浪費
沒有辦法原生監(jiān)測請求的進度,而 XHR 可以
1.4.基于 Promise 的 axiosaxios 算是請求框架中的明星項目了。目前 github 5w+ 的 star...
先來看看有什么特性吧~
同時支持瀏覽器端和服務端的請求。(XMLHttpRequests、http)
支持 Promise
支持請求和和數(shù)據(jù)返回的攔截
轉換請求返回數(shù)據(jù),自動轉換JSON數(shù)據(jù)
支持取消請求
客戶端防止 xsrf 攻擊
嗯,看起來確實是居家旅行全棧開發(fā)必備好庫,但是 axios 并不支持 jsonp...
1.5.不得不用的 jsonp在服務器端不方便配置跨域頭的情況下,采用 jsonp 的方式發(fā)起跨域請求是一種常規(guī)操作。
在此不探究具體的實現(xiàn),原理上來說就是
由于 script 標簽可以設置跨域的來源,所以首先動態(tài)插入一個 script,將 src 設置為目標地址
服務端收到請求后,根據(jù)回調函數(shù)名(可自己約定,或作為參數(shù)傳遞)將 json 數(shù)據(jù)填入(即 json padding,所以叫 jsonp...)。例如 callback({ "foo": "bar" })。
瀏覽器端收到響應后自然會執(zhí)行該 script 即調用該函數(shù),那么回調函數(shù)就收到了服務端填入的 json 數(shù)據(jù)了。
上面講到新項目一般都棄用 jQuery 了,那么跨域請求還是得發(fā)呀。所以可能你還需要一個發(fā)送 jsonp 的庫。(實踐中選了 fetch-jsonp,當然其他庫也可以)
綜上,日常開發(fā)在框架的使用上以 axios 為主,實在不得不發(fā) jsonp 請求時,就用 fetch-jsonp。這就是我們中間層的基礎,即“武器”部分。
1.6.小程序場景在小程序場景沒得選,只能使用官方的 wx.request 函數(shù)...
二、構建接口層基礎功能對于簡單的頁面,直接裸寫請求地址也沒毛病。但是一旦項目變大,頁面數(shù)量也上去了,直接在頁面,或是組件中裸寫接口的話,會帶來以下問題
代碼冗余:很多接口請求都是類似的代碼,有許多相同的邏輯
不同的庫和場景下的接口寫法不同(ajax、jsonp、小程序...)
不方便切換測試域名
不方便編寫接口注釋
沒法實現(xiàn)統(tǒng)一攔截器、甚至中間件功能
如何封裝這些接口呢?2.1.接口地址劃分
首先我們來分析一下接口地址的組成
https://example-base.com/foo/create
https://example-base.com/foo/modify
https://example-base.com/foo/delete
對于以上地址,在 tua-api 中一般將其分為3部分
host: "https://example-base.com/"
prefix: "foo"
pathList: [ "create", "modify", "delete" ]
2.2.文件結構apis/ 一般是這樣的文件結構:
. └── apis ├── prefix-1.js ├── prefix-2.js ├── foo.js // <-- 以上的 api 地址會放在這里 └── index.js
index.js 作為接口層的入口,會導入并生成各個 api 然后再導出。
2.3.基礎配置內(nèi)容所以以上的示例接口地址可以這么寫
// src/apis/foo.js export default { // 請求的公用服務器地址。 host: "http://example-base.com/", // 請求的中間路徑,建議與文件同名,以便后期維護。 prefix: "foo", // 接口地址數(shù)組 pathList: [ { path: "create" }, { path: "modify" }, { path: "delete" }, ], }
這時如果想修改服務器地址,只需要修改 host 即可。甚至還能這么玩
// src/apis/foo.js // 某個獲取頁面地址參數(shù)的函數(shù) const getUrlParams = () => {...} export default { // 根據(jù) NODE_ENV 采用不同的服務器 host: process.env.NODE_ENV === "test" ? "http://example-test.com/" : "http://example-base.com/", // 根據(jù)頁面參數(shù)采用不同的服務器,即頁面地址帶 ?test=1 則走測試地址 host: getUrlParams().test ? "http://example-test.com/" : "http://example-base.com/", // ... }2.4.配置導出
下面來看一下 apis/index.js 該怎么寫:
import TuaApi from "tua-api" // 初始化 const tuaApi = new TuaApi({ ... }) // 導出 export const fooApi = tuaApi.getApi(require("./foo").default)
這樣我們就把接口地址封裝了起來,業(yè)務側不需要關心接口的邏輯,而后期接口的修改和升級時只需要修改這里的配置即可。
2.5.接口參數(shù)與接口類型示例的接口地址太理想化了,如果有參數(shù)如何傳遞?
假設以上接口添加 id、from 和 foo 參數(shù)。并且增加以下邏輯:
foo 參數(shù)默認填 bar
from 參數(shù)默認填 index-page
delete 接口使用 jsonp 的方式,from 參數(shù)默認填 delete-page
modify 接口使用 post 的方式,from 參數(shù)不需要填
哎~,別急著死,暫且看看怎么用 tua-api 來抽象這些邏輯?
// src/apis/foo.js export default { // ... // 公共參數(shù),將會合并到后面的各個接口參數(shù)中 commonParams: { foo: "bar", from: "index-page", }, pathList: [ { path: "create", params: { // 類似 Vue 中 props 的類型檢查 id: { required: true }, }, }, { path: "modify", // 使用 post 的方式 type: "post", params: { // 寫成 isRequired 也行 id: { isRequired: true }, // 接口不合并公共參數(shù),即不傳 from 參數(shù) commonParams: null, }, }, { path: "delete", // 使用 jsonp 的方式(不填則默認使用 axios) reqType: "jsonp", params: { id: { required: true }, // 這里填寫的 from 會覆蓋 commonParams 中的同名屬性 from: "delete-page", }, }, ], }
現(xiàn)在來看看業(yè)務側代碼有什么變化。
import { fooApi } from "@/apis/" // 直接調用將會報錯,因為沒有傳遞 id 參數(shù) await fooApi.create() // 請求參數(shù)使用傳入的 from:id=1&foo=bar&from=foo-page await fooApi.create({ id: 1, from: "foo-page" }) // 請求參數(shù)將只有 id:id=1 await fooApi.modify({ id: 1 }) // 請求參數(shù)將使用自身的 from:id=1&foo=bar&from=delete-page await fooApi.delete({ id: 1 })2.6.接口重命名
假設現(xiàn)在后臺又添加了以下兩個新接口,咱們該怎么寫配置呢?
remove/all
add-array
首先,把后臺同學砍死...2333
這什么鬼接口地址,直接填的話會業(yè)務側就會寫成這樣。
fooApi["remove/all"] fooApi["add-array"]
這代碼簡直無法直視...讓我們用 name 屬性,將接口重命名一下。
// src/apis/foo.js export default { // ... pathList: [ // ... { path: "remove/all", name: "removeAll" }, { path: "add-array", name: "addArray" }, ], }
更多配置請點擊這里查看
三、高級功能一個接口層僅僅只能發(fā) api 請求是遠遠不夠的,在日常使用中往往還有以下需求
發(fā)起請求時展示 loading,收到響應后隱藏
出錯時展示錯誤信息,例如彈一個 toast
接口上報:包括性能和錯誤
添加特技:如接口參數(shù)加密、校驗
3.1.小程序端的 loading 展示小程序端由于原生自帶 UI 組件,所以框架內(nèi)置了該功能。主要包括以下參數(shù)
isShowLoading
showLoadingFn
hideLoadingFn
顧名思義,就是開關和具體的顯示、隱藏的方法,詳情參閱這里
3.2.基礎鉤子函數(shù)最簡單的鉤子函數(shù)就是 beforeFn/afterFn 這倆函數(shù)了。
beforeFn 是在請求發(fā)起前執(zhí)行的函數(shù)(例如小程序可以通過返回 header 傳遞 cookie),因為是通過 beforeFn().then(...) 調用,所以注意要返回 Promise。
afterFn 是在收到響應后執(zhí)行的函數(shù),可以不用返回 Promise。
注意接收的參數(shù)是一個【數(shù)組】 [ res.data, ctx ]所以默認值是 const afterFn = ([x]) => x,即返回接口數(shù)據(jù)到業(yè)務側
第一個參數(shù)是接口返回的數(shù)據(jù)對象 { code, data, msg }
第二個參數(shù)是請求相關參數(shù)的對象,例如有請求的 host、type、params、fullPath、reqTime、startTime、endTime 等等
3.3.middleware 中間件鉤子函數(shù)有時不太夠用,并且代碼一長不太好維護。所以 tua-api 還引入了中間件功能,用法上和 koa 的中間件很像(其實底層直接用了 koa-compose)。
export default { middleware: [ fn1, fn2, fn3 ], }
首先說下中間件執(zhí)行順序,koa 中間件的執(zhí)行順序和 redux 的正好相反,例如以上寫法會以以下順序執(zhí)行:
請求參數(shù) -> fn1 -> fn2 -> fn3 -> 響應數(shù)據(jù) -> fn3 -> fn2 -> fn1
簡單說下中間件的寫法,分為兩種
普通函數(shù):注意一定要 return next() 否則 Promise 鏈就斷了!
async 函數(shù):注意一定要 await next()!
// 普通函數(shù),注意一定要 return next() function (ctx, next) { ctx.req // 請求的各種配置 ctx.res // 響應,但這時還未發(fā)起請求,所以是 undefined! ctx.startTime // 發(fā)起請求的時間 // 傳遞控制權給下一個中間件 return next().then(() => { // 注意這里才有響應! ctx.res // 響應對象 ctx.res.data // 響應的數(shù)據(jù) ctx.reqTime // 請求花費的時間 ctx.endTime // 收到響應的時間 }) } // async/await async function (ctx, next) { ctx.req // 請求的各種配置 // 傳遞控制權給下一個中間件 await next() // 注意這里才有響應響應! ctx.res // 響應對象 }
其他參數(shù)參閱這里
四、小結這篇安利文,先是從前端發(fā)請求的歷史出發(fā)。一步步介紹了如何構建以及使用 api 中間層,來統(tǒng)一管理接口地址,最后還介紹了下中間件等高級功能。話說回來,這么好用的 tua-api 各位開發(fā)者老爺們不來了解一下么?
參考文獻Jquery ajax, Axios, Fetch區(qū)別之我見
傳統(tǒng) Ajax 已死,F(xiàn)etch 永生
fetch 沒有你想象的那么美
fetch 使用的常見問題及解決方法
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/99151.html
摘要:并且數(shù)據(jù)同步后默認會保存下來,這樣下次再請求時存儲層中就有數(shù)據(jù)了。以下參數(shù)會傳到中這么一來,存儲層就和接口層對接起來了。五支持永久保存在某些場景下,可能不方便寫過期時間,這時默認可以傳遞,標記該數(shù)據(jù)永不過期。 零、問題的由來 開門見山地說,這篇文章【又】是一篇安利軟文~,安利的對象就是 tua-storage。 顧名思義,這就是一款存儲數(shù)據(jù)的工具。 用 tua-storage 好處大大...
摘要:在這一假設之下,是一個新奇的觀點編排才是容器生態(tài)的中心,而引擎就我們所知,只是一個開發(fā)工具。是特有的概念,但容器生態(tài)系統(tǒng)必須采用這個概念。 showImg(https://segmentfault.com/img/remote/1460000007157260?w=640&h=480); 開源項目 CRI-O ,其前身為 OCID ,官方簡介是 OCI-based implementa...
摘要:無需使用服務器實時動態(tài)編譯,而是使用預渲染方式,在構建時簡單地生成針對特定路由的靜態(tài)文件。與可以部署在任何靜態(tài)文件服務器上的完全靜態(tài)單頁面應用程序不同,服務器渲染應用程序,需要處于運行環(huán)境。更多的服務器端負載。 目錄結構 -no-ssr-demo 未做ssr之前的項目代碼用于對比 -vuecli2ssr 將vuecli生成的項目轉為ssr -prerender-demo 使用prer...
摘要:源網(wǎng)頁說明文檔所有關于你應該且必須知道的。性能和優(yōu)化概述的兼容性旨在兼容多種不同版本的支持的兼容性地理框架打算成為世界級的地理框架。其目標是盡可能簡單地構建應用程序并利用空間使能數(shù)據(jù)的功能。 源網(wǎng)頁:https://docs.djangoproject.co... django說明文檔 所有關于django你應該且必須知道的。 第一步 你是否django編程新手,那就從此開始!從零開始...
閱讀 732·2021-11-23 09:51
閱讀 2429·2021-10-11 11:10
閱讀 1298·2021-09-23 11:21
閱讀 1089·2021-09-10 10:50
閱讀 881·2019-08-30 15:54
閱讀 3325·2019-08-30 15:53
閱讀 3286·2019-08-30 15:53
閱讀 3185·2019-08-29 17:23