原生 fetch 請求失敗後(如無網路)狀態會變成 reject 走 .catch 。絕大多數情況下業務場景只需要給個 toast 等簡單處理。每個請求都 .catch 會顯得格外繁瑣,並且如果不 .catch, .then 裡面的後續處理不會觸發,可能會導致邏輯中斷。
基於上述情況,可以封裝公共請求方法處理異常情況,返回固定格式 { code, data, massage }, 只需在 .then 裡面獲取資料並處理。
目標
- 保留 fetch 語法不變
- 返回 promise,且狀態一定變成 resolve,返回固定資料格式 { code, data, message }
- 給 url 動態新增域名
- 預設失敗彈出 toast,且提供開關
- 預設請求中 loading,且提供開關
實現
實現如下
/**
* 返回 Promise({ code, data, message});
*/
import { message } from 'antd';
import { dispatch } from '../index';
import { startLoading, endLoading } from '../store/globalSlice';
// 在 config/env.js 中設定
export const origin = process.env.ORIGIN;
interface MyInit extends RequestInit {
noToastError?: boolean; // 預設false
noLoading?: boolean; // 預設false
}
// 請求返回統一格式
export class Response {
code: number;
data: any;
message: string | null;
constructor(code: number, data: any, message: string | null) {
this.code = code;
this.data = data;
this.message = message;
}
}
export default (input: RequestInfo, myInit?: MyInit) => {
const init: MyInit = myInit || {};
// url 動態新增 origin
if (typeof input === 'string') {
if (!/https?:\/\//.test(input)) {
input = origin + input;
}
}
// 開始 loading
if (!init?.noLoading) {
dispatch(startLoading());
}
// 請求
return fetch(input, {
headers: {
'Content-Type': 'application/json',
},
...init,
})
.then((resp) => {
// code 不為 2xx
if (!/^2\d{2}$/.test(String(resp.status))) {
return new Response(resp.status, null, null);
}
return resp
.json()
.then((json) => {
// toast錯誤資訊
if (json.code !== 0) {
message.error(json.message);
}
// 退出登陸
if (json.code === 20001) {
// logout();
// window.location.pathname = '/login';
}
// 請求成功的資料
return json;
})
.catch((err) => {
// 不是 json 格式
return new Response(999998, null, 'data is not json.');
});
})
.catch((err) => {
// 請求傳送失敗
message.error('您的網路可能不通,請確認後重試');
return new Response(999999, null, err);
})
.finally(() => {
// 結束 loading
if (!init?.noLoading) {
dispatch(endLoading());
}
});
};
目前只完成了對 json 資料的處理。
其中 loading 部分用了 redux 和 @reduxjs/toolkit,用一個變數記錄當前請求個數,控制實際 loading 開始和結束,實現如下:
let count = 0;
export const startLoading = () => (dispatch: any) => {
if (count === 0) {
dispatch(setLoding(true));
}
count++;
};
export const endLoading = () => (dispatch: any) => {
count--;
if (count === 0) {
dispatch(setLoding(false));
}
};
使用
import myfetch from './myfetch.ts';
myfetch('/xxx/xxx')
.then(res => console.log(res))
myfetch('https://www.xxx.com/xxx/xxx')
.then(res => console.log(res))
myfetch('/xxx/xxx', {
methods: 'POST',
body: JSON.stringify({}),
})
.then(res => console.log(res))
myfetch('/xxx/xxx', {
methods: 'POST',
body: JSON.stringify({}),
noToastError: true,
noLoading: true,
})
.then(res => console.log(res))