开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
前言
本篇文章主要介绍在本项目中 wx.request
网络请求的封装与使用,以及微信小程序开发者文档中一些疑问点的释义。
本篇文章对应代码分支为 http
知识点
wx.request 与 wx.uploadFile 的封装
微信文档中 Promise 的释义
一、wx.request 介绍
wx.request
是小程序提供的网络请求方法,其结构有点像 jquery
的 ajax
方法,都是以回调函数的形式返回接口请求状态。
微信小程序 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.request
的dataType
默认值为json
、content-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
的封装,该封装可能不会满足每个项目的业务,但个人觉得也不必面面俱到,适合当前的项目就是好的。
如您有更好的意见或建议,请在评论区留言。