fetch的基本使用
/*
向服务器发送数据请求的方案:
第一类: XMLHttpRequest
+ ajax
+ axios 第三方库,对XMLHttpRequest进行封装(基于promise进行封装)
第二类: fetch
ES6内置的API,本身就是基于promise,用全新的方案实现客户端和服务器端的数据请求
+ 不兼容IE
+ 机制不够XMLHttpRequest完善(无法设置超时时间,没有内置的请求中断处理)
第三类:其他方案,主要是跨域为主
+ jsonp
+ postMessage
+ 利用img的src发送请求,实现数据埋点和上报
+ ....
let promise实例(p) = fetch(请求地址, 配置项);
+ 和axios的区别
+ 在fetch中,只要服务器有反馈信息(不管HTTP状态码是多少), 都说明网络请求成功, 最后的实例p都是fulfilled, 只有服务器没有任何反馈(例如:请求中断、请求超时、断网等), 实例p才是rejected!
+ 在axios中,只有返回的状态码以2开头,才会让实例是成功态!
+ fetch没有对GET请求,问号传参信息做特殊的处理(axios中基于params设置问号参数信息),fetch需要自己手动拼接到url末尾
+ 所以进入then的第一个回调,不一定说明请求成功,需要自行判断
+ 配置项
+ method 请求的方式, 默认是GET
+ cache 缓存模式 <>
+ credentials 资源凭证(例如cookie) <include, *same-origin, omit>
fetch默认下,跨域请求中, 不允许携带资源凭证, 只有同源下才允许
include: 同源和跨域都行
omit: 都不行
+ headers: 普通对象{}/Headers实例
自定义请求头信息
+ body 设置请求主体信息
+ 只适用于POST系列请求, 在GET系列请求中设置body会报错(让返回的实力变为失败态)
+ body内容格式有要求, 需要在请求头中指定 Content-Type 值
+ application/json JSON字符串
+ application/x-www-form-urlencoded
'xxx=xxx&xxx=xxx'
+ multipart/form-data
FormData对象,主要运用在文件上传(或者表单提交)的操作中
let fm = new FormData();
fm.append('file', 文件);
// fm就是FormData对象
+ text/plain 普通字符串
+ ....
+ Headers头处理类, 请求头或者响应头
Headers.prototype
+ append 新增头信息
+ delete 删除头信息
+ forEach((val, key) => {})
+ get 获取某一项的信息
+ has 验证是否包含某一项
+ ....
*/
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('name', 'zhufeng');
let p = fetch('/api/getTaskList?state=2', {
/* headers: {
'Content-Type': 'application/json'
} */
headers,
});
p.then(res => {
/*
res是服务器返回的response对象(Response类的实例)
私有属性:
+ body 响应主体信息 是一个 ReadableStream 可读流
+ headers 响应头信息 Headers类的实例
+ status/statusText 返回的HTTP状态码和描述
Response.prototype
+ arrayBuffer
+ blob
+ formData
+ json
+ text
+ ....
这些方法是用来处理body可读流信息的, 把响应主体信息可读流信息转换为我们自己需要的格式!!
这些方法的返回值是promise实例,可以避免服务器返回的信息在转换中出现问题(例如:服务器返回的是一个流信息,我们转化为json对象是不对的,此时可以让其返回失败的实例即可)
*/
// {type: 'basic', url: 'http://localhost:3000/api/getTaskList', redirected: false, status: 200, ok: true, …}
// {type: 'basic', url: 'http://localhost:3000/api/getTaskList1', redirected: false, status: 404, ok: false, …}
const { headers, status, statusText } = res;
console.log(headers.get('Date'));
if(/^(2|3)\d{2}$/.test(status)) { // 代表成功
// console.log('成功->', res.json()); // 这个方法不能重复调用
return res.json();
}
// 获取数据失败
return Promise.reject({
code: -100,
status,
statusText
});
// throw new Error('获取数据失败了!');
}).then(res => {
console.log('最终结果->', res);
}).catch(err => {
// 失败的原因
// 1. 服务器没有返回任何信息
// 2. 状态码不对
// 3. 数据转换失败
console.log('失败->', err);
})
封装fetch请求库
/*
http([config]);
+ url 请求地址
+ method
+ credentials 携带资源凭证 *include, same-origin, omit
+ headers: null 自定义请求头信息 必须是纯粹对象
+ body: null 请求主体信息 只针对与POST系列请求 根据当前服务器要求 如果用户传递的是一个纯粹对象 需要转为urlencoded格式字符串
+ params: null 设定问号传参信息 格式必须是纯粹对象 在内部我们将其拼接在url的末尾
+ reponseType 预设服务器返回结果的读取方式 *json/text/arrayBuffer/blob
+ signal 中断请求的信号
----------
http.get/head/delete/options([url], [config]) 预先指定了预置项中的url/method
http.post/put/patch([url], [body], [config]) 预先指定了预置项中的url/method/body
----------
e.g.
http.get('/api/xxx', {...});
http({
method: 'GET',
url: '/api/xxx',
...
});
http.post('/api/xxx', {}, {...});
http({
method: 'POST',
url: '/api/xxx',
body: {},
...
});
*/
import { isPlainObject } from '../assets/utils'; // isPlainObject判断对象是不是一个纯对象
import qs from 'qs';
import { message } from 'antd';
const baseURL = '/api';
const http = function http(config) {
if (!isPlainObject(config)) {
config = {};
}
/* 设定默认值 */
config = Object.assign({
url: '',
method: 'GET',
credentials: 'include',
reponseType: 'json',
headers: null,
body: null,
params: null,
signal: null
}, config);
/* 规则校验 */
if (!config.url) {
throw new TypeError('url is required!');
}
if (!isPlainObject(config.headers)) {
config.headers = {};
}
if (config.params !== null && !isPlainObject(config.params)) {
config.params = null;
}
/* 开始处理 */
let { url, method, credentials, headers, body, params, signal, reponseType } = config;
// 处理问号传参
url = baseURL + url;
if (params) {
url += `${url.indexOf('?') === -1 ? '?' : '&'}${qs.stringify(params)}`; // xxx=xxx&xxx=xxx
}
// 处理请求主体, 如果传递的是普通对象, 需要将其设置成urlencoded格式字符串, 如果是json格式字符串, 需要用 JSON.stringify() 进行转换
if (isPlainObject(body)) {
body = qs.stringify(body); // 已做 encodeURIComponent 处理
headers['Content-Type'] = 'application/x-www-form-urlencoded';
// headers['Content-Type'] = 'application/json';
}
// 统一处理,类似axios的请求拦截
let token = localStorage.getItem('token');
if (token) {
headers['authorization'] = token;
}
// 整理配置项准备发送fetch请求
method = method.toUpperCase();
config = {
method,
credentials,
headers,
signal,
cache: 'no-cache'
}
if (/^(POST|PUT|PATCH)$/.test(method) && body) {
config.body = body;
}
return fetch(url, config)
.then(response => { // 响应拦截
let { status, statusText } = response;
if (/^(2|3)\d{2}$/.test(status)) { // 请求成功
let res;
// *json/text/arrayBuffer/blob
switch(reponseType) { // 这些函数执行也可能因为流转换失败,返回失败promise实例
case 'text':
res = response.text();
break;
case 'arrayBuffer':
res = response.arrayBuffer();
break;
case 'blob':
res = response.blob();
break;
default:
res = response.json();
}
return res;
}
// 请求失败 HTTP状态码失败
return Promise.reject({
code: -100,
status,
statusText
});
})
.catch(reason => { // 失败的统一提示
if(reason && typeof reason === 'object') {
let { code, status } = reason;
if(code === -100) { // 状态码出错
switch(+status) {
case 400:
message.error('请求参数出现问题!');
break;
case 401: // 未登录 token过期 无权限
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
message.error('未授权,请重新登录!');
localStorage.removeItem('token');
break;
case 403:
message.error('服务器拒绝访问!');
break;
case 404:
message.error('网络请求不存在!');
break;
default:
message.error(`出错了!错误原因是 ${reason.status}: ${reason.statusText}`);
}
} else if(code === 20) { // 请求被中断
message.error('请求被中断了~');
} else {
message.error('当前网络繁忙,请您稍后再试试吧~');
}
} else {
message.error('当前网络繁忙,请您稍后再试试吧~');
}
// http.get('...').catch(() => {...})
return Promise.reject(reason);
})
};
// get系列的快捷方法
['GET', 'HEAD', 'DELETE', 'OPTIONS'].forEach(method => {
http[method.toLowerCase()] = function (url, config) {
if (!isPlainObject(config)) {
config = {};
}
config['url'] = url;
config['method'] = method;
return http(config);
}
});
// post系列的快捷方法
['POST', 'PUT', 'PATCH'].forEach(method => {
http[method.toLowerCase()] = function (url, body, config) {
if (!isPlainObject(config)) {
config = {};
}
config['url'] = url;
config['body'] = body;
config['method'] = method;
return http(config);
}
});
export default http;