npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mp-req

v1.0.0

Published

基于wx.request的高级封装。

Readme

mp-req

mp-req是wx.request的高级封装,用于发起ajax请求。

wx.request是一个底层api,使用的不便之处在于:

  1. 返回结果比较底层,需要处理statusCode,而开发者往往更关注业务相关的data部分;
  2. 登录机制繁琐,设计上甚至有些反人类;
  3. 不具备良好的接口管理功能,可维护性差;

……

综上所述,wx.request需要一层高级的封装来简化操作,因此有了mp-req(以下简称req),req代理了wx.request,并在这基础上做了一些设计工作,以提供良好的维护性:

  • promisify:支持promise,替代callback的方式;
  • 简化respone:简化返回的数据信息,只保留业务数据;
  • method替代url:使用js api的书写方式来替代直接书写url的方式;
  • 接口缓存:支持便捷的接口前端缓存;
  • 自动登录:登录态过期自动重新登录,过程对开发者透明。

下载与安装

点击这里下载mp-req的源码,将解压后的文件夹拷贝到小程序项目中的utils目录下,之后我们在项目根目录下创建文件夹req,新建文件req/index.js,引用mp-req并初始化:

const req = require('../utils/mp-req/index.js');

req.init({
  // ...
});

module.exports = req;

如果你想要快速启动模板,对方表示没问题并向你扔了个quick-start

使用

我们先来简单演示一下用法,首先我们假定条件是这样的:

  1. 我们有一个获取用户数据的接口:https://api.jack-lo.com/mp-req/user/getInfo
  2. 调用方式为GET,参数为id

使用wx.request来调用是这样的:

Page({
  onLoad() {
    this.getUserInfo('123');
  },
  getUserInfo(id) {
    wx.request({
      url: `https://api.jack-lo.com/mp-req/user/getInfo?id=${id}`,
      success(res) {
        console.log(res);
      },
    });
  },
});

接下来我们使用req来调用。

首先,我们分析接口:

  1. 接口归类:获取用户信息应该属于user类;
  2. 方法定义:获取信息,即getInfo
  3. 入参:接受一个字符串的id

期望的效果大概是这样的:

Page({
  onLoad() {
    this.getUserInfo('123');
  },
  getUserInfo(id) {
    req.user.getInfo({
      id,
    })
      .then((res) => {
        console.log(res);
      })
      .catch((err) => {
        console.log(err);
      });
  },
});

OK,我们在项目根目录下新建一个req/api目录用来存放api的定义文件,然后新建user.js文件,并做如下定义:

// 定义类和方法
const user = {
  getInfo() {
    // your code
  },
};

接下来我们将以插件的方式实现一个install方法,这个方法负责将user这个类挂载到req上。

它接受两个参数reqrequest,req就是我们最终对外暴露的实例,而request则是对wx.request这一方法的封装:

// 实现一个安装函数install
function install(req, request) {
  req.user = {
    getInfo(data) {
      const url = 'https://api.jack-lo.com/mp-req/user/getInfo/user/getInfo';
      return request({
        url,
        method: 'GET',
        data,
      });
    },
  };
}

request接受两个参数,一个是options,这个与wx.request的入参一致,第二参数是keepLogin,是否保持登录状态,默认为true,意思是发送请求的时候携带登录状态,如果登录失效,会自动走登录流程,再重新发起一次请求;如果设置false则不需要登录状态,也就是接口调用不关心用户是否登录。

定义好install之后,我们将其暴露以供外部调用:

// 实现一个安装函数install
function install(req, request) {
  req.user = {
    getInfo(data) {
      const url = 'https://api.jack-lo.com/mp-req/user/getInfo/user/getInfo';
      return request({
        url,
        method: 'GET',
        data,
      });
    },
  };
}

module.exports = {
  install,
};

这样,我们就实现了一个接口的定义,接下来需要将这个插件安装到req上,新建文件req/index.js,使用req.use方法来接入插件:

const req = require('./utils/mp-req/index.js');
const userApi = require('./api/user.js');

req.use(userApi);

就这样,我们完成了接口的定义,接下来我们在页面中使用:

const { req } = require('../../req/index.js');

Page({
  onLoad() {
    this.getUserInfo('123');
  },
  getUserInfo(id) {
    req.user.getInfo({
      data: {
        id,
      },
      success(res) {
        console.log(res);
      },
    });
  },
});

以上,我们把一个url调用转换成了js api的调用,并且对接口进行的分类和抽象。

其实这里为了快速让大家认识req的使用,我们省略了req的初始化过程,所以上面的一波操作其实是不会work的。。。

这会儿我们再来了解一下req的初始化过程~

req.init接受三个参数,分别是:

  • apiUrl:api地址的前缀,例如:https://api.jack-lo.com/mp-req
  • code2sessionId:code(来自于wx.login)转化为sessionId的过程函数;
  • isSessionAvailable:sessionId是否有效(未过期)的判断函数。

先来看code2sessionId,我们首先需要通过wx.login获取到code,再通过wx.request将code传给后端,最后拿到后端返回的sessionId:

function code2sessionId(code) {
  return new Promise((res, rej) => {
    wx.request({
      url: `https://api.jack-lo.com/mp-req/sys/login`,
      method: 'POST',
      data: {
        code,
      },
      success(r1) {
        if (r1.data && r1.data.code === 0) {
          res(r1.data.data.sessionId);
        } else {
          rej(r1);
        }
      },
      fail: rej,
    });
  });
}

注意,code2sessionId需要返回一个promise,并且最终resolve的是sessionId

再来看isSessionAvailable,假设当sessionId过期之后,后端都会返回code=3000,那么我们就需要统一处理这个状态,来让req重新发起一次登录过程,获取新的sessionId,再重新发送这次请求,所以isSessionAvailable的实现其实很简单:

function isSessionAvailable(res) {
  return res.code !== 3000;
}

整理一下,我们回到req/index.js

const req = require('./utils/mp-req/index.js');
const userApi = require('./api/user.js');

const apiUrl = 'https://api.jack-lo.com/mp-req';

req.init({
  apiUrl,
  code2sessionId(code) {
    return new Promise((res, rej) => {
      wx.request({
        url: `${apiUrl}/sys/login`,
        method: 'POST',
        data: {
          code,
        },
        success(r1) {
          if (r1.data && r1.data.code === 0) {
            res(r1.data.data.sessionId);
          } else {
            rej(r1);
          }
        },
        fail: rej,
      });
    });
  },
  isSessionAvailable(res) {
    return res.code !== 3000;
  },
});

req.use(userApi);

由于初始化的时候我们传了apiUrl,之后的api定义我们就可以统一使用apiUrl来拼装url了,我们修改一下原来的user.js

// 实现一个安装函数install
function install(req, request) {
  req.user = {
    getInfo(data) {
      const url = `${req.apiUrl}/user/getInfo/user/getInfo`;
      return request({
        url,
        method: 'GET',
        data,
      });
    },
  };
}

module.exports = {
  install,
};

以上,就是定义一个接口的全部过程,当然,只是在第一次定义一个类的时候过程麻烦些,后续有属于user类的接口,只需要在user.js文件中补充对应的方法就可以了。

promisify

涉及到ajax就避不开异步编程,谈到异步,怎么少得了promise,所以我们第一时间考虑将其promise化:

req.user.getInfo({
  id: '123',
})
  .then((res) => {
    console.log(res);
  })
  .catch((err) => {
    console.log(err);
  });

简化respone

为了更加通用,wx.request的返回值包含了完整的respone内容,但在大部分情况下,开发者关注的只有respone.data部分,于是我们做了一层过滤,req的请求返回结果就是respone.data,至于异常的statusCode(指的是除了(statusCode >= 200 && statusCode < 300) || statusCode === 304;以外的情况),我们将它归为了fail的范畴,也就是promise的catch通道。

我们还是以上面的示例代码来介绍,wx.request返回的res结构为:

{
  "data": {
    "name": "Jack",
    "age": 18,
    "gender": 1
  },
  "statusCode": 200,
  "header": {}
}

如果我们想要读取data的内容,首先要判断statusCode是否为“正常”的http状态码,也就是诸如200之类的,而如果是404,我们还得弹个窗报个错什么的:

// url方式
wx.request({
  url: 'https://api.jack-lo.com/mp-req/user/getInfo?id=123',
  success(res) {
    if (res.statusCode === 200) {
      // 读取res.data
    } else {
      // 处理异常
    }
  },
  fail(err) {
    // 处理异常
  },
});

一般来说,我们的res.data里面还有业务层面的错误信息,这样的话,除了处理wx.requestfail的错误,以及success里异常的statusCode错误,我们还要再处理业务逻辑的res.data.code(这里假设你的数据结构是{code: number, data: any, msg: string})错误。。。

真是丧心病狂。。。

req.user.getInfo返回的res则仅为:

{
  "code": 0,
  "data": {
    "name": "Jack",
    "age": 18,
    "gender": 1
  },
  "msg": "success"
}

只关注业务部分的json,而fail和“bad statusCode”则一概交给catch通道去处理:

// method方式
req.user.getInfo({
  id: '123',
})
  .then((res) => {
    console.log(res);
    if (res.code === 0) {
      // 请求成功
    } else {
      // 请求失败
    }
  })
  .catch((err) => {
    console.log(err);
  });

此时,err有可能是fail或者“bad statusCode”产生的,而这两种情况产生的err结构并不一样,如果你想弹窗显示错误信息,你可能需要对err进行识别和提炼,为此我们内置了两个方法req.err.pickerreq.err.show,前者用于提炼错误信息文本,请放心,req.err.picker囊括了常见的error,能够很好地结合框架工作,而后者更方便,直接就是将error提炼并弹窗显示:

req.user.getInfo({
  id: '123',
})
  .then((res) => {
    console.log(res);
    if (res.code === 0) {
      // 请求成功
    } else {
      // 请求失败
      req.err.show(res.msg);
    }
  })
  .catch((err) => {
    req.err.show(err); // 弹窗显示错误信息
    console.log(req.err.picker(res)); // 打印错误信息
  });

method替代url

直接使用wx.request必然要面临手写url的问题,一方面书写不方便,另一方面难以维护。想象一下,一旦需要更换某个使用频率较高的接口,你可能要把每个调用的地方都修改一遍,而且由于url的拼接方式可能各不相同,使用find&replace功能可能会有纰漏。

// url方式
wx.request({
  url: 'https://api.jack-lo.com/mp-req/user/getInfo?id=123',
});

// method方式
req.user.getInfo({
  id: '123',
});

我们将url转化为js api,这样的好处是方便调用和维护,同时,我们将接口做了一个归类,比如获取用户信息属于user类,将来user类也会继续添加其他一些接口,这样的接口更加的语义化,同时也起到命名空间的作用。

接口缓存

某些接口使用频率高但是变动又少,比如“获取当前用户的个人信息”、“获取省市区数据”,我们可以在前端通过缓存来提高性能,为此我们提供了如下几个api来控制接口缓存:

| api | 参数 | 返回值 | 示例 | 描述 | | - | - | - | - | - | | req.cachify | [string]req api | [function]cachifyFn | req.cachify('user.getMyInfo')() | 调用接口并缓存数据 | | req.clearCache | [string]req api, [string]id(optional) | undefined | req.clearCache('user.getMyInfo') | 清除某个接口的缓存:接受两个参数,第一参数为req api名,第二参数为id(选填),也就是接口的唯一标识,这一般用在分页接口,默认可不填 | | req.clearAllCache | [string]req api(optional) | undefined | req.clearAllCache('user.getMyInfo')() | 清除所有缓存:接受一个参数req api(选填),当传值时,清除指定api的缓存,不传则清除所有api的缓存 |

我们假设你已经定义好了 “获取当前用户的个人信息”这一接口req.user.getMyInfo,我们要对这一接口进行调用后缓存,那么调用方式应该为:

req.cachify('user.getMyInfo')()
  .then((res) => {
    if (res.code === 0) {
      // res.data
    } else {
      req.err.show(res.msg);
    }
  })
  .catch((err) => {
    req.err.show(err);
  });

req.cachify接受的第一参数为一个req api名,并返回一个函数,这个函数入参同被定义的req api一致。

那么,什么时候清除缓存?当我对我的个人信息进行编辑并提交成功以后,我就需要清除缓存,以便获取最新的数据,假设已经定义好的“更新我的信息接口”为req.user.updateMyInfo

req.user.updateMyInfo()
  .then((res) => {
    if (res.code === 0) {
      req.clearCache('user.getMyInfo');
    } else {
      req.err.show(res.msg);
    }
  })
  .catch((err) => {
    req.err.show(err);
  });

注意:接口缓存是基于已定义接口的前提下,没有定义的接口是无法直接使用req.cachify调用的。

自动登录

按照官方文档,小程序的登录流程应该是这样的:

登录流程

简单来理解,小程序登录其实就是一个用code换取session_key的过程

当session_key过期了,我重新用新code换取新的session_key,再去发请求。

那么,session_key过期我们怎么知道?有些开发者可能会用wx.checkSession去定时检查,但是定多长的时间呢?不知道,因为微信不会把 session_key 的有效期告知开发者,而是根据用户使用小程序的行为对 session_key 进行续期,用户越频繁使用小程序,session_key 有效期越长,因此,定时刷新是个不好的实践,因为你把握不了时机,会造成资源浪费并且增加不确定性。

事实上,只有在需要跟微信(后端)接口打交道的时候,才需要有效的session_key,那么后端肯定知道什么时候过期了,因为微信后端会告诉我们,所以我们把过期的判断交给后端,只要后端被告知过期了,接口就返回一个固定的状态,比如code=3000,前端收到这一状态之后,便重新走一遍登录流程,获取到新的session_key,再重新发起请求。

大多数时候我们只停留在自己的业务里,并不需要跟微信打交道,我们可以约定自己的会话有效期,并且放宽一些,比如1天,只要是不需要跟微信打交道,这个时效性就会宽松的多,性能也会得到提高。

req的自动登录就是这么实现的,约定好登录过期状态(默认是res.code === 3000,请根据实际情况自行修改),req会自动调用wx.login重新获取js code,再用js code去调用登录接口换取新的sessionId,最后再发起一遍上次的请求。

这让开发者可以更加专注在业务开发上,而不必关心登录过期的问题。