简单的微信小程序 request 请求封装

前言

微信小程序开发,网络请求使用小程序内置 api wx.request() 。我们知道微信小程序的方法调用风格基本都是将回调函数作为配置传入方法参数。常用 axios 作为网络请求的同学更希望拥有一个Promise 风格的 api,网上也有一些将微信小程序 api 转为 Promise 的库,但在这里更希望明白实现这样一个功能的步骤,所以我封装了一个简易的服务,供大家参考。

创建 HttpService 服务类

请求的 method 有很多,在类中我们创建了一个工厂方法 __factory() ,用于生成不同类型的请求,并暴露出来。

export class HttpService {
  constructor() { }

  // method: OPTIONS | GET | HEAD | POST | PUT | DELETE | TRACE | CONNECT
  __factory(method) {
    return (url, data, config) => {
      return new Promise((resolve, reject) => {
        wx.request({
          ...config,
          url,
          method,
          data,
          success(res) {
            resolve(res);
          },
          fail(event) {
            reject(event);
          }
        });
      })
    }
  }

  get = this.__factory('GET');

  post = this.__factory('POST');

  del = this.__factory('DELETE');
}

这样一个简单的 Promise 风格 api 就封装出来了,使用如下:

const http = new HttpService();
http.get(url, data, config).then(res => {})

增加拦截器功能

拦截器包括请求拦截器响应拦截器,请求拦截器在请求发送之前可以对请求参数做修改和转换;响应拦截器包括成功响应(http status 以 2 开头,如:200、201、202等,详细参考 这里)和错误响应(http status 一般以4或5开头,4开头的为客户端错误,5开头的为服务端错误,常见的如:400、401、404、500、503等)。

  1. 改造构造函数
    为构造函数声明一个 options 参数,接收服务的统一设置,并对 options 参数做解构操作,获取传入的值,没有则定义默认值,最后赋值给私有成员 __options 供其他方法使用。options 参数中定义了 wx.request 的全局默认 timeout 和拦截器对象 interceptor
export class HttpService {
  __options = {}; // 存储服务配置

  constructor(opts = {}) {
    const { timeout = 0, interceptor } = opts;
    const {
      request = cfg => cfg,
      response = res => res
    } = interceptor || {};

    this.__options = {
      timeout: timeout || 0,
      interceptor: {
        request,
        response
      }
    }
  }
  // ...
}
  1. 请求拦截
    本质就是在请求发出之前对请求的配置做处理,将请求参数传入请求拦截器,请求拦截器执行后返回一个新的参数配置。
const { timeout, interceptor } = this.__options;
const params = interceptor.request({
  ...config,
  url,
  method,
  data,
  timeout,
  header: {}
});
wx.request({
  ...params,
  // ... other params
})
  1. 响应拦截器
    我们在 wx.request() 参数的 success 中做成功响应,fail 中做错误响应,返回对象中增加一个 success 字段标识成功还是失败。
wx.request({
  // ...
  success(res) {
    resolve(interceptor.response({
      ...res,
      success: true
    }));
  },
  fail(event) {
    reject(interceptor.response({
      ...event,
      success: false
    }));
  }
});

增加 abort 功能

到目前为止,我们的封装已经已经完全能够使用了,但是有一些应用场景,比如我们发起了一个请求,但是服务端响应比较慢,在这期间如果页面发生了跳转,这时候请求返回就不再需要做响应处理了,所以我们就需要一个终止请求的方法。
参照微信官方文档可以知道,wx.request() 返回一个 RequestTask 对象,此对象的 abort() 方法用于中断请求任务,刚好是我们需要的,但是经过封装后的请求返回的是一个 Promise 对象,不再是 RequestTask 对象,这时候就需要做一些兼容处理了。
对比 axios ,它使用的是一个 CancelToken 工厂实现取消,我们也可以模仿一个,实现代码参考下方的 HttpTask

完整代码

  1. 任务管理类 /core/provider/http.task.js,暴露了 abort()getTask() 两个方法
export class HttpTask {
  static TASKS = {}; // 用于存储 RequestTask

  constructor() {
    this.id = Math.random().toString(36).substr(2); // 对每个任务生产随机 id
  }

  // 用于中断/取消请求
  abort() {
    if (HttpTask.TASKS[this.id]) {
      HttpTask.TASKS[this.id].abort();
      delete HttpTask.TASKS[this.id];
    }
  }

  // 获取 RequestTask 实例
  getTask() {
    return HttpTask.TASKS[this.id];
  }
}
  1. 服务类 /core/provider/http.service.js
    小程序中 method 支持很多方法,这里我们只暴露了 getpostdelete 3个方法,如果需要其它方法,可以按照格式添加。
import { HttpTask } from './http.task';

export class HttpService {
  __options = {}; // 存储服务配置

  constructor(opts = {}) {
    const { timeout = 0, interceptor } = opts;
    const {
      request = cfg => cfg,
      response = res => res
    } = interceptor || {};

    this.__options = {
      timeout: timeout || 0,
      interceptor: {
        request,
        response
      }
    }
  }

  /**
   * 请求方法构造工厂
   * @param { OPTIONS | GET | HEAD | POST | PUT | DELETE | TRACE | CONNECT } method
   * @return { (url: string; data: any; config: object;) => Promise<any> }
   */
  __factory(method) {
    return (url, data, config) => {
      return new Promise((resolve, reject) => {
        const { timeout, interceptor } = this.__options;

        const params = interceptor.request({
          ...config,
          url,
          method,
          data,
          timeout,
          header: {}
        });
        // DELETE 请求参数处理
        if (method === 'DELETE') {
          const params = Object.entries(data).map(([key, val]) => `${key}=${val}`).join('&');
          params.url += params.url.includes('?') ? params : `?${params}`;
          params.data = undefined;
        }
        const task = wx.request({
          ...params,
          data: params.data || undefined,
          success(res) {
            resolve(interceptor.response({
              ...res,
              success: true
            }));
          },
          fail(event) {
            reject(interceptor.response({
              ...event,
              success: false
            }));
          }
        });
        if (config.taskId) {
          HttpTask.TASKS[config.taskId] = task;
        }
      })
    }
  }

  get = this.__factory('GET');

  post = this.__factory('POST');

  del = this.__factory('DELETE');
}

使用

  1. 创建服务实例 /core/service/http.js,并添加全局参数配置和拦截器配置
import { HttpService } from '../provider/http.service';

export default new HttpService({
  timeout: 6000,
  interceptor: {
    // 请求拦截
    request(config) {
      config.header.Authorization = wx.getStorageSync('token');
      return config;
    },
    // 响应拦截
    response(event) {
      // 请求错误处理
      if (!event.success) {
        wx.showToast({
          title: event.errMsg,
          icon: 'none'
        });
        // 如果返回值为空,则请求的 catch 不会触发
        return event;
      }
      // 请求成功处理
      const {
        statusCode,
        data: {
          code,
          data,
          msg = '业务异常',
        }
      } = event;
      switch (statusCode) {
        case 200:
          switch (code) {
            case 200:
              return Promise.resolve(data);
            default:
              wx.showToast({
                title: msg,
                icon: 'none'
              });
          }
          return Promise.reject(msg);
        case 401:
          wx.showToast({
            title: 'token 已失效,请重新登录!',
            icon: 'none'
          });
          // other code
          break;
        default:
          wx.showToast({
            title: `response exception: ${statusCode}`,
            icon: 'none'
          });
          return Promise.reject(event);
      }
    },
  }
})
  1. 创建业务请求服务类 /service/user.js
import http from './http';
import app from '../config/app';

/**
 * 获取用户列表
 * @param { object } params 请求参数
 * @return { Promise<User[]> }
 */
export async function getUserList(params) {
  return http.get(`${app.prefix}/users`, {
    page: 1,
    rows: 10,
    ...params
  }).then(res => Promise.resolve(res.list));
}

/**
 * 获取用户详情
 * @param {number} id 
 * @return { Promise<User> }
 */
export async function getUserById(id) {
  return http.get(`${app.prefix}/users/${id}`);
}
  1. pages 页面中使用
    现在的网络速度已经很快了,中断请求的应用场景较少,在开发初期可以不作考虑。
import { getUserById } from '../../core/service/user';

Page({
  // ...
  onLoad(options) {
    getUserById(options.id).then(res => {
      console.log('user', res);
    });
  }
})
  1. 中断请求
    我们先改造一下服务类,方法中增加一个 taskId 参数
export async function getUserById(id, taskId) {
  return http.get(`${app.prefix}/users/${id}`, {}, { taskId });
}

然后改造页面方法,只需要 new 一个 HttpTask 实例,并将 id 传入请求方法即可,然后就可以直接通过 task.abort() 直接中断请求,或者通过 task.getTask() 获取微信 RequestTask 对象。

import { HttpTask } from '../../core/provider/http.task';
import { getUserById } from '../../core/service/user';

Page({
  // ...
  onLoad(options) {
    const task = new HttpTask();
    getUserById(options.id, task.id).then(res => {
      console.log('user', res);
    });
    this.setData({ task });
  },
  onUnload() {
    this.data.task && this.data.task.abort();
  }
})

总结

到目前为止,已经能够应付大部分的开发任务了,如果不满足,可以在此基础上自行扩展或留言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容