Fetch请求的介绍
developer.mozilla.org/zh-CN/docs/…
只要服务器有响应,不论HTTP状态码为多少,Fetch都会把promise实例设置为成功
response 是Response内置类的实例
- status/statusText 状态码及其描述
- headers 是Headers内置类的实例,基于Headers.prototype上的方法,可以获取响应头的信息
- get([key])
- has([key])
- keys/values/entries 返回迭代器对象,基于next方法执行可以一次获取响应头的信息
- forEach 循环迭代每一个返回的响应头信息
- body 存储的是响应主体信息,它是一个ReadableStream可读流
Response.prototype
- arrayBuffer 以Buffer格式数据读取
- blob
- json
- text
- ...
执行这几个方法,返回的结果是一个promise实例;原因:服务器返回的数据内容格式和我们要读取的方法可能存在误差,例如:服务器返回的是普通文本,而我们基于json方法去获取,想要获取json对象,这样是无法正常读取出来的,此时可以把promise标记为失败...而且这样读取的过程也可以是异步操作的,一旦本次执行了某个方法,则无法再执行其他的方法
服务器没有响应:断开请求 & 网络出现故障,Fetch才会把promise实例设置为失败
- 基于AbortController断开请求 err={message: 'The user aborted a request.',code:20, name:'AbortError'}
- 如果从服务器成功获取内容(状态码以2、3开始的),但是读取数据失败,也会进入这里(err是Error对象,具备message属性记录失败原因)
- 如果从服务器获取的内容,但是状态码不符合要求,也会进入到这里(err是自定义的信息对象)
Fetch在当初设计的时候,并没有设置超时和断开,没有类似于XHR的监控上传下载进度
Fetch的兼容性也比较差IE都不支持,想要兼容 基于@bebal/polyfill 是不够的,还需要基于 fetch-polyfill 处理
- 不考虑兼容的情况下
- 目前实现fetch的中断处理,可以基于AbortController实现
XMLHTTPRequest相对完善的
/* 测试fetch应用 */
const controller = new AbortController(); // 创建一个控制器
fetch('/api/news_latest', {
signal: controller.signal, // 传递控制器的信号
})
.then((response) => {
let { status, statusText } = response;
if (status >= 200 && status < 400) {
return response.json();
}
return Promise.reject({
code: 'STATUS ERROR',
status,
statusText,
});
})
.then((res) => {
console.log('成功:', res);
})
.catch((err) => {
console.log(err);
});
controller.abort();
options配置
fetch('/api/user', {
method: 'GET', // 设置请求方式, 默认是GET
credentials: 'include', // 设置是否允许携带资源凭证 omit都不允许,same-origin只允许同源,include允许跨域
headers: {
'Content-Type': 'application/json',
}, //
// body:{}, // 只有在POST/PUT请求下才允许设置body(设置请求主体,但是需要再headers中指定对应的类型的Content-Type值(MIME类型))
// signal: xxx, // 用于取消请求
cache: 'no-cache', // 设置缓存模式
mode: 'cors', // 设置请求模式
})
.then((response) => {
let { status, statusText, headers, url, ok } = response;
if (status >= 200 && status < 300) {
return response.json();
}
return Promise.reject(new Error(statusText));
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
Content-Type值(MIME类型)
- urlencoded 格式字符串
application/x-www-form-urlencoded
xxx=xxx&xxx=xxx
qs.stringify/parse 可以实现对象和urlencoded字符串之间的转换 - json 格式字符串
application/json
'{"name":"xxx", "age":xxx}'
JSON.stringify/parse 可以实现对象和json字符串之间的转换 - form-data 格式 一般用于上传文件
multipart/form-data
let formData = new FormData();
formData.append('name', 'xxx');
formData.append('age', 18);
formData.append('avatar', file); - text/plain 普通文本
text/plain
xxx - 其他类型
Fetch请求封装
/*
request([config])
+ url 请求地址
+ method 请求方法 *GET, POST, PUT, DELETE, etc.
+ credentials 携带资源凭证 include, *same-origin, omit
+ headers:null 自定义的请求头信息(格式必须是纯粹对象)
+ body:null 请求主体信息(只针对于POST系列请求,根据当前服务器要求,如果用户传递的是一个纯粹对象,我们需要把其变为urlencoded格式字符串(设定请求头中的Content-Type)...)
+ params:null 设定问号传参信息(格式必须是纯粹对象,我们在内部把其拼接到url的末尾)
+ responseType 预设服务器返回结果的读取方式 *json, text, blob, arraybuffer, etc.
+ signal 中断请求的信号(基于AbortController实现中断请求)
------
request.get/head/post/put/delete(url[, config]) 预先指定了配置项中的url/method
request.post/put/delete(url[, data[, config]]) 预先指定了配置项中的url/method/body
request.abort() 中断当前请求
*/
import qs from 'qs'
import { isPlainObject } from '@/assets/utils'
import { ElMessage } from 'element-plus'
/* 核心方法 */
const request = (config) => {
// init config
if (!isPlainObject(config)) config = {}
config = Object.assign(
{
url: '',
method: 'GET',
credentials: 'include',
headers: null,
body: null,
params: null,
responseType: 'json',
signal: null
},
config
)
// validate config (可以给每一项都做校验)
if (!config.url) return Promise.reject(new Error('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, responseType } = config
// 处理URL:params存在,我们需要把params中的每一项拼接到URL末尾
if (params) url += `${url.includes('?') ? '&' : '?'}${qs.stringify(params)}`
// 处理请求主体:只针对于POST系列请求;body是个纯粹对象,根据当前后台请求,要把其变为urlencoded格式
// 扩展:根据body传递格式的数据类型,在内部默认把Content-Type设置好
if (isPlainObject(body)) {
body = qs.stringify(body)
headers['Content-Type'] = 'application/x-www-form-urlencoded'
}
// 类似于Axios的请求拦截器,例如:把存储在客户端本地的token信息携带给服务器(根据当前后台要求处理)
let token = localStorage.getItem('token')
if (token) headers['Authorization'] = `Bearer ${token}`
// 发起请求
method = method.toUpperCase()
config = {
method,
credentials,
headers,
caches: 'no-cache',
mode: 'cors'
}
if (/POST|PUT|PATCH/.test(method) && body) config.body = body
return fetch(url, config)
.then((res) => {
// 成功则返回响应主体信息
let { status, statusText } = res,
result
if (!/^(2|3)\d{2}$/.test(status)) return Promise.reject({ code: -1, status, statusText })
switch (responseType.toUpperCase()) {
case 'TEXT':
result = res.text()
break
case 'BLOB':
result = res.blob()
break
case 'FORMDATA':
result = res.formData()
break
case 'ARRAYBUFFER':
result = res.arrayBuffer()
break
default:
result = res.json()
}
return result.then(null, (reason) => Promise.reject({ code: -2, reason }))
})
.catch((reason) => {
// 根据不同的失败情况做不同的统一提示
let code = reason?.code
if (+code === -1) {
// 状态码问题
switch (+reason.status) {
case 401:
// 未授权
ElMessage.error('未授权,请登录')
break
case 404:
// 资源不存在
ElMessage.error('资源不存在')
break
case 500:
// 服务器错误
ElMessage.error('服务器错误')
}
} else if (+code === -2) {
// 数据解析失败
ElMessage.error('数据解析失败')
} else if (+code === 20) {
// 请求被中断
ElMessage.error('请求被中断')
} else {
// 其他错误
ElMessage.error('请求失败')
}
return Promise.reject(reason)
})
}
/* 快捷方法 */
;['GET', 'HEAD', 'DELETE', 'OPTIONS'].forEach((method) => {
request[method.toLowerCase()] = function (url, config) {
if (!isPlainObject(config)) config = {}
config['url'] = url
config['method'] = method
return request(config)
}
})
;['POST', 'PUT', 'PATCH'].forEach((method) => {
request[method.toLowerCase()] = function (url, body, config) {
if (!isPlainObject(config)) config = {}
config['url'] = url
config['method'] = method
config['body'] = body
return request(config)
}
})
export default request
接口管理
import http from './http';
export const getUserInfo = (data) => {
return http.get('/api/getUserInfo', data);
};
接口测试
// 在请求发送时创建请求中断器
let ct = new AbortController()
getUserInfo({signal:ct.signal})
// 中断请求
ct.abort()