方式一
默认情况下,官方提供的异步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
添加拦截器功能,包括请求拦截器
,响应拦截器
- 请求拦截器:本质上是在请求之前调用的函数,用来对参数进行新增、修改
- 响应拦截器:本质上是响应之后调用的函数,用来处理响应数据
注意:不管响应成功还是失败,都会执行响应拦截器
请求拦截器
响应拦截器
注意:在发送请求时,还需要区分是否通过实例调用了拦截器
- 没有通过实例调用拦截器,需要定义默认拦截器,在默认拦截器中,需要将请求参数返回
- 通过实例调用的拦截器,实例拦截器会覆盖默认拦截器的方法,然后将新怎或修改的请求参数返回
// 通过类封装,提高代码复用性,也可方便添加新的属性和方法
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的显示隐藏会存在以下问题:
- 每次请求都会执行loading,但页面中只会显示一个loading,后面的
loading
会覆盖前面的loading - 同时发起请求,只要有一个请求成功响应就会调用wx.hideLoading,导致其他请求还没有完成,也不会loading
- 请求过快,或请求在另一个请求后触发,会出现loading闪烁问题
通过队列的方式解决以上问题:首先在类中新增一个实例属性queue
,初始值空数组
- 发起请求前,判断
queue
如果是空数组显示loading,然后立即响应queue
新增请求标识 - 在
complete
中每次请求成功结束,从queue
中移除一个标识。queue
为空隐藏loading
- 为了解决网络请求过快产生
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
A完成后,会触发定时器,因B请求是在A执行完成立即触发的,则A的定时器不会执行
B请求执行时,会判断queue队列是否为空,因A定时器未执行,且A完成时又添加了一个标识
所以,queue队列不为空,没有调用wx.showLoading()
loading的对象还是原来那个
因B请求又新增了一个标识,此时就会有两个标识。
B执行完成之后会删除一个标识,在判断是否为空,不为空就不会追加,进入定时器,隐藏loading
控制loading显示
实际开发中,有的接口需要自定义loading
效果,就需要关闭默认loading
效果。一般通过开关控制
- 在类内部设置默认请求参数
isLoading
属性,默认是true - 某个接口不需要展示
loading
效果,可以在发送请求时,isloading
设置为false
- 整个项目都不需要
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