在做上一个全站的管理系统时,用了完整的前后端分离开发。在项目的前期方案制定中参考了vue2-manage的部分实现,觉得有一些实现方案是自己所没有想到的比较精妙的方案。对fetchAPI的封装便是其中一个。
同时欢迎大家从我的博客阅读这篇文章,和我的其他的微不足道的创作:
Here`s Chino! - ——From dawn to dusk.
一、Fetch API
对于我这样的小白fetchAPI这一技术算是比较新了,在以前的学习中都没有接触到过,经过查询mdn文档发现这一技术确实没有很高的兼容性:
不难看出,fetchAPI对IE的兼容性尤其不好.....而edge则处于可以凑合用的状态。移动端还算乐观,兼容性略好一点。
AJAX已经相当成熟了,对于fetchAPI的存在意义,MDN的描述为“ Fetch API 提供了一个获取资源的接口(包括跨域请求)。任何使用过XMLHttpRequest的人都能轻松上手,但新的API提供了更强大和灵活的功能集。
比较通俗的讲,fetch为请求定义了Request和Response对象的标准(虽然现在兼容性依然一片狼藉)。这种标准不光满足了网络请求的需要,在应用中所有需要请求和相应的地方都可以运用,甚至是自定义一种相应也未尝不可。对于浏览器中的网络请求,window接口上直接就可以使用fetch()方法,所以它是个全局方法。
这个新颖的API返回的是Promise对象,这就意味着使用ES6的async/await新特性变为可能,可以更方便的处理逻辑方面的问题而不会产生callback hell。具体体现为:
//fetch API
let requestConfig = {
credentials: 'include',
method: type,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
mode: "cors",
cache: "force-cache"
}
const response = await fetch(url, requestConfig);
const responseJson = await response.json()
fetchAPI可以安全的使用await,相比之下,axios需要.then(),请求部分和业务代码杂糅在一起,远没有fetchAPI简洁。
二、封装
在fetchAPI的运用中我们已经体会到了fetchAPI的简洁之处,但是其缺点同样明显——兼容性不足。那么如果我们要在项目中使用fetchAPI,就需要对其进行封装,不支持fetchAPI特性的环境我们要将其转换成普通的 XMLHTTPRequest,乃至 ActiveX (IE就应该被彻底消灭啊啊啊啊)所以接下来我直接把其项目中关于fetch的封装代码帖上来,加上我认为最详尽的注释。(因为我个人也没有更好的方案了
//fetch.js
import { baseUrl } from './env'
export default async(url = '', data = {}, type = 'GET', method = 'fetch') => {
//url拼接
type = type.toUpperCase();
url = baseUrl + url;
if (type == 'GET') {
let dataStr = ''; //数据拼接字符串
Object.keys(data).forEach(key => {
dataStr += key + '=' + data[key] + '&';
})
if (dataStr !== '') {
dataStr = dataStr.substr(0, dataStr.lastIndexOf('&'));
url = url + '?' + dataStr;
}
}
if (window.fetch && method == 'fetch') {
let requestConfig = {
//携带cookie
credentials: 'include',
method: type,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
//跨域
mode: "cors",
cache: "force-cache"
}
if (type == 'POST') {
Object.defineProperty(requestConfig, 'body', {
value: JSON.stringify(data)
})
}
try {
const response = await fetch(url, requestConfig);
const responseJson = await response.json();
return responseJson
} catch (error) {
throw new Error(error)
}
} else {
//将XMLHTTPRrequest和ActiveX也用Promise的方法返回
return new Promise((resolve, reject) => {
let requestObj;
if (window.XMLHttpRequest) {
requestObj = new XMLHttpRequest();
} else {
requestObj = new ActiveXObject;
}
let sendData = '';
if (type == 'POST') {
sendData = JSON.stringify(data);
}
requestObj.open(type, url, true);
requestObj.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
requestObj.send(sendData);
requestObj.onreadystatechange = () => {
if (requestObj.readyState == 4) {
if (requestObj.status == 200) {
let obj = requestObj.response
if (typeof obj !== 'object') {
obj = JSON.parse(obj);
}
resolve(obj)
} else {
reject(requestObj)
}
}
}
})
}
}
其中值得一提的是,在拼接url的时候引用的baseURL是这样返回的
//env.js
/**
* 配置编译环境和线上环境之间的切换
*
* baseUrl: 域名地址
* routerMode: 路由模式
* baseImgPath: 图片存放地址
*
*/
let baseUrl = '';
let routerMode = 'hash';
let baseImgPath;
if (process.env.NODE_ENV == 'development') {
baseUrl = '';
baseImgPath = '/img/';
}else{
baseUrl = '//elm.cangdu.org';
baseImgPath = '//elm.cangdu.org/img/';
}
export {
baseUrl,
routerMode,
baseImgPath
}
这样就完成了开发和线上模式的切换,属实巧妙。