初识小程序-API封装

47 阅读10分钟

方式一

默认情况下,官方提供的异步API都是基于回调函数实现的,如:

      wx.request({
        method:'',
        url: 'url',
        data:{},
        success(){}, // 成功的回调函数
        fail(){},    // 失败的回调函数
        complete(){},// 请求完成的回调函数
      })

缺点:容易造成回调地域,代码的可读性、维护性差!

API Promise化

是指通过额外的配置,将官方提供、基于回调函数的异步API,升级改造为基于Promise的异步API,从而提高代码的可读性、维护性、避免回调地域的问题。 实现API promise化

下载miniprogram-api-promise

在小程序中,实现API Promise化主要依赖于miniprogram-api-promise这个第三方npm包。安装使用步骤如下:

  • 下载npm包
  • 删除miniprogram_npm文件【不删除容易出现构建失败问题】
  • 构建npm包
npm install --save miniprogram-api-promise@1.0.4

使用miniprogram-api-promise

1.在小程序入口文件中(app.js),只需要调用一次promisifyAll()方法,即可实现promise化

// app.js
import {promisifyAll} from "miniprogram-api-promise"
const wxp=wx.p={}
// 将wx对象上,所有基于异步回调的api,通过promisifyAll实现promise化,将promise化后的方法挂载到wxp对象上
promisifyAll(wx,wxp)

wxp 或 wx.p 指向同一个对象,promise化成功后,wxp 或 wx.p 存储的就是promise化后的api,就可以通过wxp 或 wx.p 调用promise化的方法

调用promise化后的API

// promise化
wx.p.request({
  url: 'https://applet-base-api-t.itheima.net/api/get',
  method:"GET",
  data:{
    name:"zs",
    age:20
  }
}).then(res=>{
  console.log("promise res",res)
})
// 原始用法
wx.request({
  url: 'https://applet-base-api-t.itheima.net/api/get',
  method:"GET",
  data:{
    name:"zs",
    age:20
  },
  success:(res)=>{
    console.log(res);
  }
})

方式二

request封装

为了方便统一处理请求参数及服务器响应结果,为request添加拦截器功能,包括请求拦截器响应拦截器

  • 请求拦截器:本质上是在请求之前调用的函数,用来对参数进行新增、修改
  • 响应拦截器:本质上是响应之后调用的函数,用来处理响应数据

注意:不管响应成功还是失败,都会执行响应拦截器

请求拦截器

响应拦截器

注意:在发送请求时,还需要区分是否通过实例调用了拦截器

  1. 没有通过实例调用拦截器,需要定义默认拦截器,在默认拦截器中,需要将请求参数返回
  2. 通过实例调用的拦截器,实例拦截器会覆盖默认拦截器的方法,然后将新怎或修改的请求参数返回
// 通过类封装,提高代码复用性,也可方便添加新的属性和方法
class WxRequest {
 ...
  // 定义拦截器对象
  // 需要包含请求拦截器以及响应拦截器,方便请求以及响应时进行逻辑处理
  interceptors={
    // 请求拦截器
    // 请求发送之前,对请求参数进行新增、修改,默认不处理
    request(config){
     return config
    },
    // 响应拦截器
    // 服务器响应数据以后,对数据处理,默认不处理
    response(response){
     return response
    }
  }
  request(options){
    // 合并完整的请求地址
    options.url=this.defaults.baseURL+options.url;

    // 合并请求参数
    options = {...this.defaults,...options}
    // 在请求之前调用请求拦截器,用于新怎或修改参数
    options=this.interceptors.request(options)

    return new Promise((resolve,reject)=>{
      wx.request({
        ...options,
        success(res){
          // 不管响应成功还是失败,都需要调用响应拦截器
          // 响应拦截器需要接收服务器响应数据,对数据进行逻辑处理,处理好后返回
          // 然后通过resolve将数据返回

          // 给响应拦截器传递参数时,需要将请求参数也一起传递
          // 方便调试及其他逻辑处理,需要先合并
          // 然后将合并的参数传给拦截器
          const mergeRes = Object.assign({},res,{config:options})
          resolve(this.interceptors.response(mergeRes))
        },
        // 一般网络异常时(如请求超时),会走fail
        fail(err){
          const mergeErr = Object.assign({},err,{config:options})
          reject(this.interceptors.response(mergeErr))
        }
      })
    })
  }

}
const instance = new WxRequest()
// 配置请求拦截器
instance.interceptors.request= config =>{
  return config
}

// 配置响应拦截器
instance.interceptors.response = response => {
  return response
}

export default instance

请求loading封装

目前每次发送请求时,请求发送之前会展示loading,响应之后会隐藏loading。但是loading的显示隐藏会存在以下问题:

  1. 每次请求都会执行loading,但页面中只会显示一个loading,后面的loading会覆盖前面的loading
  2. 同时发起请求,只要有一个请求成功响应就会调用wx.hideLoading,导致其他请求还没有完成,也不会loading
  3. 请求过快,或请求在另一个请求后触发,会出现loading闪烁问题

通过队列的方式解决以上问题:首先在类中新增一个实例属性queue,初始值空数组

  1. 发起请求前,判断queue如果是空数组显示loading,然后立即响应queue新增请求标识
  2. complete中每次请求成功结束,从queue中移除一个标识。queue为空隐藏loading
  3. 为了解决网络请求过快产生loading闪烁问题,可以使用定时器判断
class WxRequest {
  ...
  // 初识空数组,用来存储请求队列
  queue = []
  ...
  request(options){
    ...
    // 判断请求队列queue是否为空,如果是空,就显示loading
    this.queue.length===0 && wx.showLoading();
    // 向queue中添加请求标识
    // 每个标识代表是一个请求,标识是自定义的
    this.queue.push("request");
    return new Promise((resolve,reject)=>{
      // 使用wxwx.request发送请求时,
      // 只要成功接收到服务器返回结果
      // 无论状态码是多少,都会执行 success
      // 需要根据状态码,自己做判断
      wx.request({
       ...
       complete(){
          // 不管成功还是失败,隐藏loading
          wx.hideLoading();

          // 每次结束后删除该标识
          this.queue.pop();
          // 删除标识以后,需要判断queue是否为空
          // 如果请求为空,则并发请求完成,则隐藏loading
          this.queue.length === 0 && wx.hideLoading()
        }
      })
    })   
  }
}

上述代码解决并发loading问题,未解决一个请求结束后,立即调用另一个请求的loading闪烁问题

class WxRequest {
  ...
  // 初识空数组,用来存储请求队列
  queue = []
  ...
  request(options){
    // 如果有新的请求,就
    this.timer && clearTimeout(this.timer)
    ...
    // 判断请求队列queue是否为空,如果是空,就显示loading
    this.queue.length===0 && wx.showLoading();
    // 向queue中添加请求标识
    // 每个标识代表是一个请求,标识是自定义的
    this.queue.push("request");
    return new Promise((resolve,reject)=>{
      // 使用wxwx.request发送请求时,
      // 只要成功接收到服务器返回结果
      // 无论状态码是多少,都会执行 success
      // 需要根据状态码,自己做判断
      wx.request({
       ...
        complete(){
          // 不管成功还是失败,隐藏loading
          wx.hideLoading();
          // 每次结束后删除该标识
          this.queue.pop();
          this.queue.length === 0 && this.queue.push("request")
          this.timer=setTimeout(()=>{
            // 每次结束后删除该标识
            this.queue.pop();
            // 删除标识以后,需要判断queue是否为空
            // 如果请求为空,则并发请求完成,则隐藏loading
            this.queue.length === 0 && wx.hideLoading()
            clearTimeout(this.timer)
          })
        }

      })
    })   
  }
}

逻辑解析

如:两个请求AB,A请求完成后,立即调用B请求

A请求发生时,会加入一个标识request

image.png

A完成后,会触发定时器,因B请求是在A执行完成立即触发的,则A的定时器不会执行

B请求执行时,会判断queue队列是否为空,因A定时器未执行,且A完成时又添加了一个标识

image.png

所以,queue队列不为空,没有调用wx.showLoading()loading的对象还是原来那个

image.png 因B请求又新增了一个标识,此时就会有两个标识。

image.png B执行完成之后会删除一个标识,在判断是否为空,不为空就不会追加,进入定时器,隐藏loading

控制loading显示

实际开发中,有的接口需要自定义loading效果,就需要关闭默认loading效果。一般通过开关控制

  1. 在类内部设置默认请求参数isLoading属性,默认是true
  2. 某个接口不需要展示loading效果,可以在发送请求时,isloading设置为false
  3. 整个项目都不需要loading效果,可以在实例化时,isLoading配置为false
class WxRequest {
  ...
  defaults={
   ...
   // 控制是否使用默认的loading,true表示使用默认的loading
   isLoading:true, 
   ...
  }
  ...
  request(options){
    // 如果有新的请求,就
    this.timer && clearTimeout(this.timer)
    ...
    if(options.isLoading){
      // 判断请求队列queue是否为空,如果是空,就显示loading
      this.queue.length===0 && wx.showLoading();

      // 向queue中添加请求标识
      // 每个标识代表是一个请求,标识是自定义的
      this.queue.push("request");
    }
    return new Promise((resolve,reject)=>{
      // 使用wxwx.request发送请求时,
      // 只要成功接收到服务器返回结果
      // 无论状态码是多少,都会执行 success
      // 需要根据状态码,自己做判断
      wx.request({
       ...
        complete(){
          if(options.isLoading){
              // 不管成功还是失败,隐藏loading
              wx.hideLoading();
              // 每次结束后删除该标识
              this.queue.pop();
              this.queue.length === 0 && this.queue.push("request")
              this.timer=setTimeout(()=>{
                // 每次结束后删除该标识
                this.queue.pop();
                // 删除标识以后,需要判断queue是否为空
                // 如果请求为空,则并发请求完成,则隐藏loading
                this.queue.length === 0 && wx.hideLoading()
                clearTimeout(this.timer)
              })
          }
        }

      })
    })   
  }
}

完整代码

// 通过类封装,提高代码复用性,也可方便添加新的属性和方法
class WxRequest {
  // 默认参数对象
  defaults={
   baseURL:"", // 基准地址
   url:"", // 接口地址
   data:null, // 请求参数
   method:"GET",
   header:{
     "Content-type":"application/json", // 设置数据交互格式
   },
   isLoading:true, // 控制是否使用默认的loading,true表示使用默认的loading
   timeout:60000 // 默认超时时间一分钟
  }

  // 定义拦截器对象
  // 需要包含请求拦截器以及响应拦截器,方便请求以及响应时进行逻辑处理
  interceptors={
    // 请求拦截器
    // 请求发送之前,对请求参数进行新增、修改,默认不处理
    request(config){
     return config
    },

    // 响应拦截器
    // 服务器响应数据以后,对数据处理,默认不处理
    response(response){
    return response
    }

  }
  // 初识空数组,用来存储请求队列
  queue = []
  // 默认参数合并在constructor进行,将传入的参数与defaults合并,覆盖默认配置
  constructor (params={}) {
    this.defaults=Object.assign({},this.defaults,params);
  }
  request(options){
    // 如果有新的请求,就
    this.timer && clearTimeout(this.timer)
    // 合并完整的请求地址
    options.url=this.defaults.baseURL+options.url;

    // 合并请求参数
    options = {...this.defaults,...options}
    // 在请求之前调用请求拦截器,用于新怎或修改参数
    options=this.interceptors.request(options)
    if(options.isLoading){
      // 判断请求队列queue是否为空,如果是空,就显示loading
      this.queue.length===0 && wx.showLoading();
      // 向queue中添加请求标识
      // 每个标识代表是一个请求,标识是自定义的
      this.queue.push("request");
    }

    return new Promise((resolve,reject)=>{
      // 使用wxwx.request发送请求时,
      // 只要成功接收到服务器返回结果
      // 无论状态码是多少,都会执行 success
      // 需要根据状态码,自己做判断
      wx.request({
        ...options,
        success(res){
          // 不管响应成功还是失败,都需要调用响应拦截器
          // 响应拦截器需要接收服务器响应数据,对数据进行逻辑处理,处理好后返回
          // 然后通过resolve将数据返回

          // 给响应拦截器传递参数时,需要将请求参数也一起传递
          // 方便调试及其他逻辑处理,需要先合并
          // 然后将合并的参数传给拦截器

          // 新增isSuccess字段用于区分响应成功还是失败
          const mergeRes = Object.assign({},res,{config:options,isSuccess:true})
          resolve(this.interceptors.response(mergeRes))
        },
        // 一般网络异常时(如请求超时),会走fail
        fail(err){
          const mergeErr = Object.assign({},err,{config:options,isSuccess:false})
          reject(this.interceptors.response(mergeErr))
        },
        complete(){
          if(options.isLoading){
              // 不管成功还是失败,隐藏loading
              wx.hideLoading();
              // 每次结束后删除该标识
              this.queue.pop();
              this.queue.length === 0 && this.queue.push("request")
              this.timer=setTimeout(()=>{
                // 每次结束后删除该标识
                this.queue.pop();
                // 删除标识以后,需要判断queue是否为空
                // 如果请求为空,则并发请求完成,则隐藏loading
                this.queue.length === 0 && wx.hideLoading()
                clearTimeout(this.timer)
              })
          }
        }
      })
    })
  }
  get(url,data={},config={}){
    return this.request(Object.assign({url,data,method:"GET"},config))
  }
  post(url,data={},config={}){
    return this.request(Object.assign({url,data,method:"POST"},config))
  }
  put(url,data={},config={}){
    return this.request(Object.assign({url,data,method:"PUT"},config))
  }
  delete(url,data={},config={}){
    return this.request(Object.assign({url,data,method:"DELETE"},config))
  }
}
const instance = new WxRequest()
// 配置请求拦截器
instance.interceptors.request= config =>{
  // 请求发送前,添加loading效果
  wx.showLoading();

  const token="";
  if(token){
    config.header.token=token
  }
  return config
}

// 配置响应拦截器
instance.interceptors.response = response => {
  let {isSuccess,data} = response
  if(!isSuccess){
    wx.showToast({
      title: '网络异常',
      icon:"error"
    })
    return response
  }
  switch (data.code) {
    // 如果后端返回的业务状态码等于 200 说明数据响应成功
    case 200:
      return data;
    case 208: // token失效,跳回登录页
      // 清除失效token,清除本地全部信息
      // wx.clearStorage()
      // wx.navigateTo({
      //   url: 'url',
      // })    
      return Promise.reject(response);
      default :
      // 抛出异常
      break;
      

  }
  return response
}

export default instance