背景
众所周知, 微信小程序架构系统分为逻辑层 和视图层 , 所有的js逻辑是在一个单线程中运行的。 当用户切换到A页面时, A页面接口请求执行, 如果用户快速的切换到B页面,B页面请求会立即执行, 此时如果A页面的请求恰好超时了, 请求超时的toast会在B页面被抛出,会给用户造成困扰, 同时也会影响开发者调查的方向 。所以, 我们希望开发一套设计, 能够满足在跳转页面的时候, 将未完成的同时允许中断的请求中断掉,以达到优化性能同时提升用户体验的目的。
设计思路
- 小程序的页面不再直接通过Page方法来定义,而是通过统一的customPage来控制, 以达到在页面生命周期中加入统一的逻辑 , 在这里, 通过在onLoad生命周期中加入判断,来取消前一个页面的未完成的请求。
- 定义一个requestAbortTask类, 用于维护可中断请求的信息, 里面包含加入中断请求的方法, 清除实例信息的方法, 中断请求的方法
- 定义一个通用的request 方法, 所有的请求均通过request 来调用, 该方法允许配置请求是否支持中断, 对于配置了允许中断的请求, 在调用的时候将该请求abort加入到requestAbortTask实例中去
具体代码
代码里面有相应注释, 这里不做多余解释
定义customPage.js
js
export default function page(config) {
const APP = getApp()
Page({
...config,
onLoad(options = {}) {
config.onLoad && config.onLoad.call(this, options)
if (APP.globalData && APP.globalData.requestTasks) { // 这里的globalData 需要提前定义, 也可以放到其他地方中
const allPages = getCurrentPages()
const currentPage = allPages[allPages.length - 1].route
APP.globalData.requestTasks.abortOthersByKey(currentPage)
}
},
})
}
定义requestAbortTask.js
js
export class RequestAbortTask {
constructor() {
this.tasksMap = new Map()
}
getTasksByKey(key) {
return this.tasksMap.get(key)
}
addTaskByKey(key, task) {
if (!this.tasksMap.has(key)) {
const value = new Set()
value.add(task)
this.tasksMap.set(key, value)
return
}
const values = this.tasksMap.get(key)
values.add(task)
}
add(key, task) {
this.addTaskByKey(key, task)
}
removeTaskByKey(key, task) {
const tasks = this.tasksMap.get(key)
if (tasks) {
tasks.delete(task)
}
}
removeAllByKey(key) {
this.tasksMap.delete(key)
}
clear() {
this.tasksMap.clear()
}
// 通过页面key, 终止掉所有其他页面的未完成请求
abortOthersByKey(key) {
if (this.tasksMap.size) {
this.tasksMap.forEach((values, taskKey) => {
if (taskKey !== key) {
if (values.size) {
values.forEach((task) => {
if (task && task.abort) {
task.abort()
}
})
this.removeAllByKey(taskKey)
}
}
})
}
}
}
定义request 方法
js
import { RequestAbortTask } from "./requestAbortTask"
export default function request(params, options = {}) {
const { canAbort } = options
const APP = getApp()
const allPages = getCurrentPages()
const currentPage = allPages[allPages.length - 1].route
return new Promise((resolve, reject) => {
const removeAbortTask = (requestTask) => { // 如果请求在切换页面前已经完成了, 在requestTasks中移除
if (canAbort) { // 这里只对配置了canAbort 的请求做特殊逻辑
if (APP.globalData.requestTasks) {
APP.globalData.requestTasks.removeTaskByKey(currentPage, requestTask)
}
}
}
const requestTask = wx.request({
...params,
success(res) {
removeAbortTask(requestTask) // 移除该请求中断
resolve(res)
},
fail(err) {
removeAbortTask(requestTask) // 移除该请求中断
if (canAbort && err && err.errMsg === 'request:fail abort') {
// 终止了请求会进入这个逻辑,一般不用特殊处理,直接return掉
// 接口请求手动终止, 逻辑自定义
}
console.log("fail err", err)
reject(err)
}
})
if (canAbort) { // 将配置了允许中断的请求加入到requestTasks中
if (!APP.globalData.requestTasks) {
APP.globalData.requestTasks = new RequestTask()
}
APP.globalData.requestTasks.addTaskByKey(currentPage, requestTask)
}
})
}
实例:
在页面A中 调用方法
js
import page from "../../util/customPage"
import request from "../../util/request"
const app = getApp()
page({
data: {
},
onLoad() {
request({
method: "GET",
url: "https://jsonplaceholder.typicode.com/comments" // 替换实际请求
}, { canAbort: true })
},
handleNavigateTo() {
wx.navigateTo({
url: '/pages/secondPage/secondPage', // 当切换页面的时候, 如何没有请求完成的接口,会被终止掉
})
}
})
在页面B中
js
import page from "../../util/customPage"
page({
handleBack() {
wx.navigateBack()
}
})
代码片段
developers.weixin.qq.com/s/vD548gmj7...