UniApp搭建项目模板(二)—— 基础请求类型封装

11/2/2022 uniapp

# 简介

基于 UView 内置的请求框架luch-request (opens new window)做二次封装

# 功能

  1. 基础请求类型封装: get、post、put、delete [✅]
  2. 集成请求代理功能 [x]
  3. 无感刷新 token 封装 [x]
  4. 自定义后端请求 code 报错信息 [x]

# 基本变量定义

// 当前正在请求的接口队列
const HttpQueue = []
// 存储每个请求id,会被放在http queue中
let id = 0

// http: 请求使用uview-ui自带的luch-request请求,功能比uni.request完善
const http = uni.$u.http

// 请求默认10s后abort
const requestTimeoutTime = 10000
1
2
3
4
5
6
7
8
9
10

# 全局配置、请求响应拦截器

// 全局配置
http.setConfig(config => {
  return config
})

// 请求拦截
http.interceptors.request.use(
  config => {
    // 鉴权处理
    const token = uni.getStorageSync('token')
    if (token) {
      config.header['Authorization'] = token
    }
    return config
  },
  config => {
    return Promise.reject(config)
  }
)

// 响应拦截
http.interceptors.response.use(
  response => {
    return response
  },
  response => {
    return Promise.reject(response)
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 请求函数主体

function service(params) {
  let {
    // 预定义参数
    url = '',
    method = 'GET',
    data = {},
    headers = {},
    dataType = 'json',
    responseType = 'text',
    withCredentials = false,
    // 是否在报错时不提示
    silentOnError = false,
    // 是否需要显示loading
    mask = true,
    // 超时时间
    timeout = requestTimeoutTime,
    ...extra
  } = params

  // 如果接口前缀不包含/自动填充一个
  if (!url.startsWith('/')) {
    url = '/' + url
  }

  if (mask) {
    uni.showLoading({
      title: '加载中'
    })
  }

  // 请求自增
  id++

  return new Promise((resolve, reject) => {
    // 用于判断当前队列中有多少相同url请求的接口
    const apiInQueue = HttpQueue.filter(i => i.ajaxURL === url)
    if (apiInQueue.length > 1) {
      console.info('当前可能存在重复请求,请确认是否需要')
    }
    // 缓存当前请求的id
    const currentId = id
    // http.request存在this指向问题,通过bind将this重新指向到http
    let requestMethod = http.request.bind(http)
    let requestParams = {
      url: requestURL,
      method,
      data,
      header: headers,
      timeout,
      dataType,
      responseType,
      withCredentials,
      // 将params传给custom 方便拦截器获取
      custom: {
        ...params,
        currentId
      },
      getTask: (task, options) => getTaskOptions(task, options, params, currentId),
      ...extra
    }
    let args = []
    if (method === 'DOWNLOAD') {
      requestMethod = http.download.bind(http)
      const { url, ...others } = requestParams
      args = [url, others]
    } else if (method === 'UPLOAD') {
      const { url, ...others } = requestParams
      requestMethod = http.upload.bind(http)
      args = [url, others]
    } else {
      args = [requestMethod]
    }

    requestMethod(requestParams)
      .then(res => {
        // 先执行hideLoading 避免请求报错toast直接hideLoading消失
        handlePromiseFinally(currentId)
        handlePromiseThen(resolve, reject, res, params)
      })
      .catch(e => {
        handlePromiseFinally(currentId)
        handlePromiseReject(resolve, reject, e, params)
      })
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

# 其他辅助函数

缓存每一个任务

function getTaskOptions(task, options, params, currentId) {
  const { url, mask } = params
  if (mask) {
    HttpQueue.push({
      id: currentId,
      ajaxURL: url,
      task: task
    })
  }
}
1
2
3
4
5
6
7
8
9
10

统一处理请求成功回调

function handlePromiseThen(resolve, reject, res, params) {
  const { silentOnError } = params
  if (res.statusCode === 200) {
    const result = res.data

    // 将后端的范围值中的code转换为Number
    if (result.code) {
      result.code = +result.code
    }
    // 接口失败
    if (result.code !== 200) {
      if (silentOnError) {
        resolve(res.data)
        return
      }
      uni.showToast({
        title: result.message,
        icon: 'none',
        // 对于app 错误信息在底部弹出
        position: 'bottom'
      })
      reject(res)
    }
    resolve(res.data)
  }
  reject(res)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

统一处理请求失败事件

const handlePromiseReject = (resolve, reject, e, params) => {
  const data = e.data
  const { silentOnError } = params
  if (silentOnError) {
    resolve(e.data)
    return
  }
  uni.showToast({
    title: data.message,
    icon: 'none',
    // 对于app 错误信息在底部弹出
    position: 'bottom'
  })
  reject(e)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

统一处理请求完成事件

function handlePromiseFinally(currentId) {
  // 无论成功还是失败,从正在请求的队列中,移除当前请求
  const index = HttpQueue.findIndex(htp => htp.id === currentId)
  if (index > -1) {
    HttpQueue.splice(index, 1)
  }
  if (HttpQueue.length === 0) {
    uni.hideLoading()
  }
}
1
2
3
4
5
6
7
8
9
10

# 暴露事件提供使用

export default {
  get(params) {
    return service({
      ...params,
      method: 'GET'
    })
  },
  post(params) {
    return service({
      ...params,
      method: 'POST'
    })
  },
  delete(params) {
    return service({
      ...params,
      method: 'DELETE'
    })
  },
  put(params) {
    return service({
      ...params,
      method: 'PUT'
    })
  },
  upload(params) {
    return service({
      ...params,
      method: 'UPLOAD'
    })
  },
  download(params) {
    return service({
      ...params,
      method: 'DOWNLOAD'
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Last Updated: 11/4/2022, 6:55:40 AM