Weapp影视评分项目开发(07):wx.request 请求封装

1,655 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

前言

本篇文章主要介绍在本项目中 wx.request 网络请求的封装与使用,以及微信小程序开发者文档中一些疑问点的释义。

本篇文章对应代码分支为 http

知识点

wx.request 与 wx.uploadFile 的封装 微信文档中 Promise 的释义

一、wx.request 介绍

wx.request 是小程序提供的网络请求方法,其结构有点像 jqueryajax 方法,都是以回调函数的形式返回接口请求状态。

微信小程序 wx.request 的官方文件中有一句:

以 Promise 风格调用:不支持

我看到网上有许多人误解了这句话的意思,以为小程序不支持 Promise。它实际是说 wx.request 方法不支持 Promise 形式调用,他返回的不是一个 Promise,所以也就没有 .then .catch 这样的方法了。
我在 2018 年刚接触微信小程序的时候,就有使用 Pormise 封装后的 wx.request 方法,封装后也是支持 await 方法的调用的,但最早是什么时候支持的,我也没有找到确切的信息,所以需要使用 Promise 的场景,也无需引入三方库。

二、wx.request 封装

1. 封装的优势:

  • 能够以 Promise 风格使用 wx.request ,包括支持 await 方法,避免层级嵌套出现回调地狱;
  • 统一接口请求与接口响应的处理,避免每个请求中存在大量重复代码;
  • 便于请求接口的封装。

2. 请求方法介绍

  • 请求类型
    本项目接口采用 restful 风格,请求会包含 get post put delete 四种类型,(本项目接口最早还有部分是 patch 类型,在规范和 axios 中是支持的,但是小程序不支持,所以最终这部分接口改为了 put)。
    小程序接口中还有个 options 类型,它正常不会在接口中定义,而是浏览器一类的宿主环境,在接口请求前,主动发送给服务器以检测服务器支持哪些 HTTP 方法,或者是当接口请求存在跨域(CORS)时,浏览器等发起一个预检请求,以检测实际请求是否可以被服务器所接收。

  • 接口鉴权
    本项目接口的鉴权采用 jwt 方案,所有接口都支持 header 中传入 token , 鉴权分为三种情况:

    • 接口不接收 token
      如首页,后端接收该字段,但不会做相关鉴权校验
    • 接口可接收可不接收 token
      如用户评论浏览,当传入 token 时则校验是否为当前用户发布等,未传入则不做校验
    • 接口必须传入 token
      如用户发布评论,必须登录用户才可评论,此时若未传入 token 或者 token 过期,接口会返回 401 状态码。

3. request 请求封装

本项目的接口请求封装主要实现以下几点:

  • token 能自动传入;
  • 接口支持 Promise 风格调用,支持 await 方法;
  • 统一处理接口返回异常值;
  • 方法调用起来便捷且可扩展。

wx.requestdataType 默认值为 jsoncontent-type 默认值为 application/json ,与本项目的接口一致,所以不需要在 header 中设置。

// config.js
export const HOST = 'https://test-h5-api.ixook.com'; // 接口域名
// app.js
App({
  token: ""  // 默认值为空,当登录或注册成功后,会将后端返回的 token 值写入
})
// http/index.js
import { HOST } from '../config'  // 导入域名
const app = getApp();  // token 存在全局 `app` 下

/**
 * @desc 接口请求封装 
 * @param {string} url 接口地址(不包含域名)
 * @param {object} [data] 请求参数
 * @param {string} [method="GET"] 请求方法,目前支持 get、post、put、delete 四种
 * @param {object} [header] 请求头信息,默认为 Authorization
 * @return {promise} 请求结果 promise
 */
function request(url, data = {}, method = 'GET', header = {}) {
  // 校验是否存在 token,不存在则不传入
  if (app.token) {
    header = Object.assign(header, {
      // 注意 Bearer 后有一个空格
      Authorization: `Bearer ${app.token}`
    })
  }
  
  // 返回一个 Pormise ,使其支持 Promise 调用
  return new Promise((resolve, reject) => {
    wx.request({
      url: HOST + url,
      data,
      method,
      header,
      success: ({statusCode, data}) => {
	// 用户未登录或者 token 过期,则返回登录页
        if (statusCode === 401) {
          setTimeout(() => {
            wx.navigateTo({
              url: "/pages/account/login/index"
            })
          }, 0)
        }

	// 请求成功
        if (statusCode === 200) {
	// 业务状态码大于400,则为异常,并显示对应异常提示信息
          if (data.code >= 400) {
            wx.showToast({
              title: String(data.message || 'system error'),
              icon: 'none',
              duration: 2000
            })
          }
	  // 返回该异常值
          resolve(data)
        }

	// 其它 http 级异常,返回异常值
        resolve({
          code: statusCode,
          message: data.message,
          data: {}
        })
      },
      fail: err => {
        // 失败时返回错误信息,这样使用端不需要 try catch 接收
        reject({
          code: err.errno || 400,
          message: err.errMsg,
          data: {}
        })

        // 异常提示
        wx.showToast({
          title: err.errMsg,
          icon: 'error',
          duration: 2000
        })
      }
    })
  })
}

4. upload 请求封装

除去以上的接口请求外,一般还会用到图片上传,比如用户更换头像,也需要封装,代码如下:

// http/index.js
/**
 * @desc 文件上传请求封装 
 * @param {string} url 上传接口地址(不包含域名)
 * @param {string} filePath 要上传文件资源的路径 (本地路径)
 * @param {string} name 文件名称值 key
 * @param {object} [formData] HTTP 请求中其他额外的 form data
 * @param {number} [timeout=60000] 超时时间,单位为毫秒
 * @return {promise} 上传结果 promise
 */
function upload(url, filePath, name = 'file', formData = {}, timeout = 60000) {
  // 返回一个 Pormise ,使其支持 Promise 调用
  return new Promise((resolve, reject) => {
    wx.uploadFile({
      url: HOST + url,
      filePath,
      name,
      formData,
      header: {
        // 上传接口必传 token ,后端会校验该值是否合法
        // 注意 Bearer 后有一个空格
        Authorization: `Bearer ${app.token}`
      },
      timeout,
      success: ({ data }) => {
        if (typeof data === 'string') {
          data = JSON.parse(data)
        }
        resolve(data)
      },
      fail: err => {
        // 失败时返回错误信息,这样使用端不需要 try catch 接收
        reject({
          code: err.errno || 400,
          message: err.errMsg,
          data: {}
        })

        // 异常提示
        wx.showToast({
          title: err.errMsg,
          icon: 'error',
          duration: 2000
        })
      }
    })
  })
}

5. 接口统一抛出

我们将以上的两个方法对外统一导出,以便于接口发起请求时调用。

注意,upload 方法也在此处统一导出,使接口保持一致

// http/index.js
export default {
  // get 请求
  get(url, data, header = {}) {
    return request(url, data, 'GET', header)
  },
  // post 请求
  post(url, data, header = {}) {
    return request(url, data, 'POST', header)
  },
  
  // put 请求
  put(url, data, header = {}) {
    return request(url, data, 'PUT', header)
  },
  
  // 删除请求
  delete(url, data, header = {}) {
    return request(url, data, 'DELETE', header)
  },
  
  // 上传请求
  upload(url, filePath, name, formData) {
    return upload(url, filePath, name, formData)
  }
}

三、封装后的使用

1. 请求接口的封装

我们之前在项目的根目录下建了个 api 文件夹,里面将会存放按模块划分的接口请求方法,我们以用户相关的接口为例,我们在 api 文件夹下创建一个 user.js, 我们实现一个用户注册接口,它是一个 post 请求,我们首先需要引入上面的接口请求封装文件并命名为 http,代码如下:

// api/user.js
// 引入请求接口封装,并命名为 http
import http from '../http/index';

// 实现注册方法,只需要传入账号信息作为参数,并导出该方法即可
export const register = (params) => http.post(`/register`, params); // 用户注册

2.请求接口的使用

我们还是以注册接口为例,这一次我们需要在注册页面对应的 js 文件中引入上面的注册方法:

注意:微信小程序的 js 导入文件不支持绝对路径,这点确实很令人头疼,网上也有人利用 require 的特性做了封装使封装后的 require 方法支持绝对路径,但编辑器就无法识别该方法的路径了,我个人不推荐这种方式。

// pages/accouts/register/index.js
import { register } from "../../../api/user"; // 层级较深时确实有些麻烦,好在编辑器有智能提示

Page({
  data: {
    form: {
      account: "",  // 手机号
      password: "", // 初始密码
      code: "",     // 手机验证码
    }
  },
  // 注册
  async register() {
    const {code, data, message } = await register(this.data.form);
    if (code === 200) {
      // 注册成功的业务处理
    } else {
     // 注册失败的业务处理
    }
  }
})

以上即为接口封装后的简单使用示例,实际场景中会加入业务的校验等处理,我们将在项目的实际开发中再细作描述。

最后

本篇文章实现了 wx.request 的封装,该封装可能不会满足每个项目的业务,但个人觉得也不必面面俱到,适合当前的项目就是好的。
如您有更好的意见或建议,请在评论区留言。