提到 axios 都不陌生,由之前的 XMLHttpRequest -> $.ajax
-> Fetch 發展,看來 Promise 是趨勢,而項目中也是由 vue-resource 的 this.$http
"淨化" 成了 axios ,但實際項目中不只是引用 axios 發送請求那麼簡單,往往還需要進行封裝,以下就分享下我們項目中使用 axios 都做了哪些事。
當然不涉及到項目任何隱私,只是純粹的技術,對,很純粹。
封裝 Service 層
不直接引用 axios 發送請求,而是統一調用 service.js
,在 Service 層裏會做很多事。
創建 Service
-
const service = axios.create({ timeout: 10000, headers: { post: { 'Content-Type': 'application/json;charset=UTF-8', }, }, emulateJSON: true, withCredentials: true, });
使用 axios.create
創建一個實例,並且配置一些 JSON 、超時、跨域之類的屬性。
國際化
統一使用 service.interceptors.request.use
攔截,並注入國際化標識,比如 request.headers['Accept-Language']
或者 request.params.lang
等。
規範化後端數據結構
在 service.interceptors.response.use
裏統一後端返回結構,比如最終會序列化成:
{
success: Boolean,
code: Number,
message: String,
data: Any,
}
這樣好處是說業務層不需要關心各個服務端返回的是否正確,而規範化後端數據時,需要跟各個服務端溝通好返回的字段標識,以方便來轉成規範化結構,比如A端認爲 code === 200
纔是成功,B端認爲 code=0
纔是成功。
接口容錯
對於前端服務而言,我們認爲不應該出現報錯到控制檯,而應該更友好的展示出來,甚至於把錯誤信息上報到監控中心,在 Service 對外暴露 POST 、GET 方法時,統一使用 try
攔截,如:
/**
* 重構 service 輸出,爲了兼容接口異常,導致js崩潰
*/
export default {
async get(...options) {
try {
const res = await service.get(...options);
return res;
} catch (e) {
return Promise.resolve({
success: false,
code: 500,
message: '服務出錯,請稍候重試',
});
}
},
async post(...options) {
try {
const res = await service.post(...options);
return res;
} catch (e) {
return Promise.resolve({
success: false,
code: 500,
message: '服務出錯,請稍候重試',
});
}
},
};
這樣在業務層就可以直接使用數據判斷,而不用再 try
或者 .catch
處理,如:
const res = await service.get(uri);
// 絕對是成功
if (res.success) {
} else {
alert(res.message);
}
當然這一偉大的邏輯是 @遠高 添加,非常厲害。
統一鑑權
因爲有規範化後端數據結構,並且結合 service.interceptors.request.use
攔截,可以很方便的對後端鑑權進行處理,比如:A端的A接口返回失敗則自動跳轉到登錄頁。甚至可以統一處理帳戶的黑、白名單。
URI 統一化
經過以上處理後整個 Service 整個都縱享絲滑,但項目應用時發現:
- 針對某些接口處理事情時比較繁瑣,需要不斷的向攔截層添加代碼
- 接口鏈接更換時,很依賴全局查找
針對以上的痛點,我們添加了 URI 前綴統一化,處理邏輯是添加一層配置層,把 URI 的前綴使用統一字符替換,如:@a/
、@b/
這些分別代表不同的接口方,而在使用時可以直接使用這些標識去請求, Service 層會統一處理替換,如:
export const api = {
'@a/': {
url: 'https://www.demo.com/api/a/',
// 自定義統一 headers
headers(service.config) {
return {
};
},
// 自定義統一參數
params(service.config) {
return {
};
},
// 自定義統一返回值
response(service.config, response) {},
},
};
這樣處理後在使用時只需要 service.get('@a/user/mm');
即可,甚至配置裏還可以根據當前的環境進行分發到不同的接口中,萌萌噠~
Node.js 代理
Service 層處理之後,如果使用 CORS 即可直接生效,但現在很多服務都是反向代理,可以使用 webpack 的 devServer.proxy
,如:
// config/proxy.js
/**
* 本地開發、測試配置
*
* @type {Object}
*/
const dev = {
'/api/a/': {
target: '目標開發環境',
changeOrigin: true,
onProxyReq(req) {
req.setHeader('origin', '目標開發環境');
},
},
};
/**
* 線上生產模式
*
* @type {Object}
*/
const prod = {
'/api/a/': {
target: '目標生產環境',
changeOrigin: true,
onProxyReq(req) {
req.setHeader('origin', '目標生產環境');
},
},
};
// 暴露 dev 代理
exports.dev = dev;
// 暴露生產代理
exports.prod = prod;
// 根據環境暴露不同的代理
exports.proxy = IS_PROD ? prod : dev;
在 devServer.proxy
直接引用對應的環境配置即可。
你會發現還配置了 onProxyReq
,是因爲 changeOrigin
只是會修改請求的 host
,代碼見:node-http-proxy ,而很多後端服務是有源 origin
限制,這樣就可以穿透了。
注意:Service 是運行在瀏覽器端,而 Node.js 代理只是本地開發時爲了接口方便進行的代理,Service 把 @a/
標識替換成請求本地的絕對鏈接 /api/a/xxx
(假如不是 CORS 外鏈域名),然後進入 Node.js 代理,而線上大部分都是 Nginx 做的代理 。