HTTP
在过去, 要请求后端的接口需要借助很多第三方库, 比如 jquery 的 ajax, axios, 他们最大的缺点就是体积过大, 并且不是那么易用. 其中 axios 在现代框架中被广泛使用, 但是其同时实现了服务端的 api, 因此为了体积和易用性开发出这一个用于与后端进行交互的 api
快速使用
ts
import { Http } from 'cat-kit'
const http = new Http({
baseUrl: '/api', // 表示所有的接口以 /api 作为前缀
timeout: 18000 // 表示如果请求超过18000就抛出超时错误
})
例子
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
<template>
<div>
<div>需要在开发模式下在项目根目录执行pnpm docs:server命令</div>
<br />
<v-button @click="request1">发起请求</v-button> <br />
<v-button @click="request2">发起请求并使任意请求以错误的形式返回></v-button
><br />
<v-button @click="request3">
超时的请求>
<template v-if="timeout">等待{{ timeout / 1000 }}s</template>
</v-button>
<br />
<v-button @click="request4">1s后终止所有本次发起的请求</v-button>
<br />
<v-button @click="request5">DELETE请求和PUT请求转换为POST请求</v-button>
<br />
<v-button @click="request6">发起二进制请求</v-button>
<br/>
<v-button @click="request7">发起form-data请求</v-button>
</div>
</template>
<script lang="ts" setup>
import { Http, type IRequestor } from '@cat-kit/fe'
import { shallowRef } from 'vue'
const env = 'zwt'
const http = new Http({
baseUrl: '/api',
timeout: 18000,
before(conf) {
if (env === 'zwt' && ['PUT', 'DELETE'].includes(conf.method)) {
conf.headers['X-HTTP-Method-Override'] = conf.method
conf.method = 'POST'
}
return conf
},
after(res, _, resType) {
if (resType === 'error') {
console.error('抛出错误, 错误信息: ', res.message)
}
return res
}
})
const timeout = shallowRef(0)
const count = () => {
setTimeout(() => {
timeout.value -= 1000
if (timeout.value < 0) {
timeout.value = 0
}
if (timeout.value > 0) {
count()
}
}, 1000)
}
const request1 = async () => {
const res = await http.post('/test', null, {
created(req) {
console.log(req)
}
})
console.log('request1: ', res.data)
}
const request2 = async () => {
await http.post('/test', null, {
after(res, responseAs) {
responseAs('error')
}
})
console.log('request2')
}
const request3 = async () => {
timeout.value = 18000
count()
await http.post('/test', {
// 告诉服务器设置20000ms之后再响应数据
sleep: 20000
})
console.log('request3')
}
const request4 = () => {
const requests = new Set<IRequestor>()
console.time('耗时:')
Array.from({ length: 10 }).forEach((_, i) => {
const sleep = Math.random() * 2000
console.log(`第${i}个请求需要${sleep}ms返回数据`)
http
.post(
'/test',
{
sleep
},
{
created(req) {
requests.add(req)
},
complete(req) {
requests.delete(req)
}
}
)
.then(() => {
console.log(`第${i}个请求完成`)
})
.catch(() => {
console.log(`第${i}个请求被终止`)
})
})
console.timeEnd('耗时:')
// 1s后将所有没有请求的响应终止
setTimeout(() => {
requests.forEach(item => item.abort())
}, 1000)
}
const request5 = () => {
http.put('/put').then(res => {
console.log(res)
})
}
const request6 = async () => {
let buffer = new ArrayBuffer(512)
let u8a = new Uint8Array(buffer)
for (let i = 0; i < u8a.length; i++) {
u8a[i] = i % 255
}
const res = await http.post('/test3', buffer, {
created(req) {
console.log(req)
},
headers: {
'Content-Type': 'application/octet-stream'
}
})
console.log('request1: ', res.data)
}
const request7 = async () => {
const formData = new FormData()
formData.append('url', 'http://192.168.31.250:9000/chunk/cd461c8890bd0a5cc5bd847321c35e51/1.chunk?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=szyh%2F20240201%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240201T055829Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=86a6064efcbc288d71075bb62b7b1ff1304d716fea76f065885fa3d1c7eb2bc4')
const buffer = new ArrayBuffer(10)
const u8a = new Uint8Array(buffer)
for (let index = 0; index < 10; index++) {
u8a[index] = index
}
const file = new File([new Blob([buffer])], 'a.js')
formData.append('file', file)
const res = await http.put('/admin/file/forwardMinio', formData, {
})
console.log('request7: ', res.data)
}
</script>
api
构造参数 new Http(options: Options)
- Options
- before
<Function>
: 生命周期, 该方法会在请求发送之前调用(如果指定的话) - after
<Function>
: 生命周期, 该方法在请求完成后调用, 无论成功还是失败 - baseUrl
<string>
: 基础请求路径 - timeout:
<number>
: 超时时间, 默认为 0 即不超时 - headers:
<Object>
: 请求头 - withCredentials:
<boolean>
: 请求是否携带 cookie, 默认为 false
- before
Http.request
ts
http
.request({
url: '/user/1',
method: 'GET'
})
.then(res => {
console.log(res.data, res.code)
})
// GET http://xxx.xxx.xxx/api/user/1
快捷写法
ts
http.get('/user', {
params: {
id: 1
}
})
// GET http://xxx.xxx.xxx/api/user?id=1
http.post(
'/user',
{ name: '张三', age: 20 },
{
params: { type: 'admin' }
}
)
// POST http://xxx.xxx.xxx/api/user?type=admin payload: { type: 'admin' }
http.put('http://www.baidu.com/user/1')
// PUT http://www.baidu.com/user/1
http.delete('/user/1')
// DELETE http://xxx.xxx.xxx/api/user/1
生命周期
axios 中叫拦截器.
before
before 是一个函数, 你可以返回一个 false 来阻止请求实例的创建, 亦可以返回一个新的配置对象, 以便于你对不同的接口进行通用的处理, 以下是一个小例子
ts
const http = new Http({
// 默认使所有的请求url之前拼接一个/api
baseUrl: '/api',
timeout: 18000,
before(conf) {
const isXWF = conf.headers['Content-Type'].includes(
'application/x-www-form-urlencoded'
)
// 对于表单对象, 你需要手动将其转化成一个key=value拼接的字符串
if (isXWF && isObj(conf.data)) {
conf.data = Object.keys(conf.data)
.map(k => `${k}=${conf.data[k]}`)
.join('&')
}
// post请求不允许什么都不穿
if (
conf.method === 'POST' &&
(conf.data === undefined || conf.data === null)
) {
console.error('请传点东西')
return false
}
// 请求之前找到
if (token) {
conf.headers['Authorization'] = 'some token'
}
// 是一个流意味着文件可能很大, 去掉超时
if (conf.responseType === 'blob') {
conf.timeout = 0
}
return conf
}
})
after
after 在请求完成后调用, 该函数有两个参数, 第一个参数是响应对象, 第二个参数指定以何种形式抛出响应值(当以 error 抛出时, 一个异步函数中的代码将不会执行), 看以下的例子: 默认地, 以 Http 标准将 400 到 600 之间的状态码作为错误抛出
ts
// 指定以下状态吗都是报错的状态码
const customErrorCode = new Set<number>([1001, 1002, 1003])
export const authHttp = new Http({
baseUrl: 'http://localhost',
after(res, returnBy) {
if (customErrorCode.has(res.code)) {
returnBy('error')
message.error(res.message)
} else {
returnBy('normal')
}
if (res.is(401)) {
router.replace('/login')
}
return res
}
})
// 假设后端返回的状态码是1001(错误状态吗)
const postTo = async () => {
await authHttp.post('/path/of/api')
// 以下不会执行, 因为authHttp将自定义错误码以错误抛出
// 换句话说以下代码是属于Promise<any>.then中回调函数执行的代码,
// 以错误抛出则只执行Promise<any>.catch中的回调函数
// 这种模式能够让你少写状态码判断的相关代码
alert('成功')
}
// 不够优雅的例子
const badPostTo = async () => {
const { code } = await authHttp.post('/path/of/api')
if (code !== 200) return // 这段代码不必要增加了很多判断
alert('成功')
}
终止请求
ts
const http = new Http()
Array.from({ length: 10 }).forEach(() => {
http.get('/path/of/api')
})
// 终止当前所有的有http发起的请求
http.abort()
// 更加细粒度的请求终止
function batchRequest() {
const requests = new Set<IRequestor>()
Array.from({ length: 10 }).forEach(() => {
http.get('/path/of/api', {
created(req) {
requests.add(req)
},
complete(req) {
requests.delete(req)
}
})
})
return requests
}
const requests = batchRequest()
// 1s后将所有没有请求的响应终止
setTimeout(() => {
requests.forEach(item => item.abort())
}, 1000)