Skip to content

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

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)

MIT Licensed