随着浏览器的更新迭代,对 fetch 和 Promise 的支持也越来越好,在一些需求小或者无兼容需求的项目引入请求库无疑是浪费资源的,而原生支持的 fetch 就为我们提供了另一条路,下面针对 fetch 和 API 规范做封装
请求基本配置
const BASE_URL = "https://xxxx.com/xxx"; // 请求地址
const TIME_OUT = 5000; // 请求超时时间,单位毫秒
先看我们的主要请求体
const fetchApi = async (url, opts) => {
const controller = new AbortController();
url = base_url + url;
const options = {
headers: {
Authorization: `Bearer ${getAccessToken()}`,
"content-type": "application/json",
},
...opts,
signal: controller.signal,
};
const timer = setTimeout(() => {
controller.abort();
}, TIME_OUT);
try {
return await fetch(url, options)
.then(async res => {
if (res.ok === true) {
const data = await res.clone().json();
return Promise.resolve(data);
} else {
if (res.status === 401) {
let params = {
refresh_token: getRefreshToken(),
};
let { access_token } = await refreshToken(params);
setAccessToken(access_token);
return await fetchApi(url, options);
} else {
return Promise.reject(res);
}
}
})
.catch(err => {
return Promise.reject(err);
})
.finally(_ => {
clearTimeout(timer);
});
} catch (err) {
return Promise.reject(err);
}
};
- fetchApi 参数 1. 请求地址 2. 初始化配置对象, 可选
- 4 行 - 11 行,拼接 url 同时初始化请求参数,初始化一般都是携带自定义的请求头
- 2、10、13-15 行,我们通过延时器和 AbortController 控制器取消请求来处理请求时间过长的问题,同时在请求完成后不要忘记清除延时器 39 行
- 为了统一处理 Promise 都是用 await 来接收结果,在外层用 try catch 捕获所有错误
- 17 行 - 40 行就是请求主体了,判断请求状态返回请求结果,如果 token 过期则刷新 token 再次请求
- 通常在 21 行处还需要判断请求成功,但因为其他原因导致失败的状态码,一般都和服务端进行协商约定,这个封装因为后端是自己的项目错误情况都不会是 200 状态码所以省略了这一步
为了便于调用,我们在原型方法上挂载请求方法函数
fetchApi.get = async (url, params = {}) => {
if (JSON.stringify(params) !== "{}") {
url += (url.indexOf("?") > -1 ? "&" : "?") + paramsStringify(params);
}
return await fetchApi(url, {
method: "GET",
});
};
fetchApi.post = async (url, data) => {
return await fetchApi(url, {
method: "POST",
body: JSON.stringify(data),
});
};
fetchApi.put = async (url, data) => {
return await fetchApi(url, {
method: "PUT",
body: JSON.stringify(data),
});
};
fetchApi.delete = async url => {
return await fetchApi(url, {
method: "DELETE",
});
};
get 方法和 delete 方法没有 body 所以我们需要自己处理参数
封装中用到的其他方法如下:
const paramsStringify = params => {
return Object.keys(params)
.map(key => {
return `${key}=${encodeURI(params[key])}`;
})
.join("&");
};
const setAccessToken = (access_token, options) => {
return Cookies.set("access_token", access_token, options);
};
const getAccessToken = () => {
return Cookies.get("access_token");
};
const getRefreshToken = () => {
return Cookies.get("refresh_token");
};
const refreshToken = params =>
fetchApi.get(`${base_url}/auth/refresh_token`, params);
完整的代码如下
const base_url = "/api";
const TIME_OUT = 30000;
const setAccessToken = (access_token, options) => {
return Cookies.set("access_token", access_token, options);
};
const getAccessToken = () => {
return Cookies.get("access_token");
};
const getRefreshToken = () => {
return Cookies.get("refresh_token");
};
const paramsStringify = params => {
return Object.keys(params)
.map(key => {
return `${key}=${encodeURI(params[key])}`;
})
.join("&");
};
const refreshToken = params => fetchApi.get(`/auth/refresh_token`, params);
const fetchApi = async (url, opts) => {
const controller = new AbortController();
url = base_url + url;
const options = {
headers: {
Authorization: `Bearer ${getAccessToken()}`,
},
mode: "cors",
...opts,
signal: controller.signal,
};
const timer = setTimeout(() => {
controller.abort();
}, TIME_OUT);
try {
return await fetch(url, options)
.then(async res => {
if (res.ok === true) {
const data = await res.clone().json();
return Promise.resolve(data);
} else {
if (res.status === 401) {
let params = {
refresh_token: getRefreshToken(),
};
let { access_token } = await refreshToken(params);
setAccessToken(access_token);
return await fetchApi(url, options);
} else {
return Promise.reject(res);
}
}
})
.catch(err => {
return Promise.reject(err);
})
.finally(_ => {
clearTimeout(timer);
});
} catch (err) {
return Promise.reject(err);
}
};
fetchApi.get = async (url, params = {}) => {
if (JSON.stringify(params) !== "{}") {
url += (url.indexOf("?") > -1 ? "&" : "?") + paramsStringify(params);
}
return await fetchApi(url, {
method: "GET",
});
};
fetchApi.post = async (url, data) => {
return await fetchApi(url, {
method: "POST",
body: JSON.stringify(data),
});
};
export default fetchApi;
使用方法
fetchApi("/api/login", { username: "monster", password: 123456 }).then(
res => xxx
);
总结
为了简洁,一个方法可以放在 utils 工具文件中进行复用,此处为了展示完整代码所以搬到了一个文件中。响应可以根据自己的需求进行扩展。因为是原生支持还是存在不同浏览器的兼容问题需要使用 polyfill 插件来解决。虽然提供了 mode 来支持跨域,但还是需要服务端来配合使用才行。