失败的网络请求封装
前情提要
之前做 java 大作业用组合式封装了网络请求,大概是这样的
ts
// request.ts
import { ref, type Ref } from 'vue';
import { type ReturnData } from '@/model/dto/ReturnData';
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL;
export interface RequestResult<T> {
data: Ref<T | undefined>;
isLoading: Ref<boolean>;
err: Ref<string>;
}
function useRequest<T>(
url: string,
requestInit: RequestInit = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
tokenIsNeeded: boolean = true
): RequestResult<T> {
const data: Ref<T | undefined> = ref<T>();
const isLoading = ref(true);
const err = ref('');
if (tokenIsNeeded) {
const token = localStorage.getItem('token');
const headers = new Headers(requestInit.headers ?? {});
headers.set('Authorization', `Bearer ${token}`);
requestInit.headers = headers;
}
fetch('https://' + apiBaseUrl + url, { ...requestInit })
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((jsonData: ReturnData<T>) => {
if (jsonData.code >= 200 && jsonData.code < 300) {
data.value = jsonData.data;
isLoading.value = false;
} else {
throw new Error(jsonData.message);
}
})
.catch((e) => {
err.value = String(e);
isLoading.value = false;
});
return { data, isLoading, err };
}
export { useRequest };
这样写的话在使用骨架屏的时候确实方便一些,isLoading 为 true 时显示骨架屏,false 的时候再展示数据。但是如果是登录或者一些不需要骨架屏的情况下就会相当的痛苦。
ts
const email = localStorage.getItem('email') as string;
const password = localStorage.getItem('password') as string;
const { data, isLoading, err } = Login({
email: email,
password: password,
});
watch(isLoading, () => {
if (err.value) {
console.log(err);
} else {
if (!isLoading.value) {
if (data.value?.user) {
setUser(data.value.user);
}
if (data.value?.token) {
localStorage.setItem('token', data.value.token);
router.push('/');
}
}
}
});
每次在函数里调用都需要使用 watch 来监听 isLoading 的变化,带来额外的负担。
解决
第一种方法
我想到的第一个方法是再封装一次组合式函数
function useAsyncRequest<T>(
url: string,
requestInit: RequestInit = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
tokenIsNeeded: boolean = true
) {
const { data, isLoading, err } = useRequest<T>(
url,
requestInit,
tokenIsNeeded
);
return new Promise((resolve, reject) => {
watch(isLoading, () => {
if (err.value) {
reject(err.value);
} else {
resolve(data.value);
}
});
})
}
这样子的话使用的时候就不需要监听 isLoading 了
ts
const email = localStorage.getItem('email') as string;
const password = localStorage.getItem('password') as string;
useAsyncRequest(
'/users/login',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
password: password,
}),
},
false
).then((data) => {
console.log(data);
// ...
});
不过这样感觉也不太好,有点多此一举的感觉,直接抛弃响应式变量应该会更好一些。
第二种方法
ts
async function useAsyncRequest<T>(
url: string,
requestInit: RequestInit = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
tokenIsNeeded: boolean = true
) {
let data = null;
let err = '';
if (tokenIsNeeded) {
const token = localStorage.getItem('token');
const headers = new Headers(requestInit.headers ?? {});
headers.set('Authorization', `Bearer ${token}`);
requestInit.headers = headers;
}
try {
const resp = await fetch('https://' + apiBaseUrl + url, {
...requestInit,
});
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
const jsonData = await resp.json();
if (jsonData.code >= 200 && jsonData.code < 300) {
data = jsonData.data;
return { data, err };
} else {
throw new Error(jsonData.message);
}
} catch (e) {
err = String(e);
return { data, err };
}
}
这样的话感觉会更好一些,但是感觉还是不太完美,可能还要再想想吧。