不知道掘友们平时是否有遇到过这种场景
在微信小程序加载首页时,同时发送多个请求,如:
wx.request('home/user/rule')
wx.request('home/user/info')
...
此类接口在调用前,可能需要传递用户信息到后端,如token、user_id这些信息。而这些信息必须得在用户登录后才能获取到。为每个接口都单独写获取token的逻辑,显然不可取。那理所当然的,大家都会选择把请求封装起来。如:
// 公用请求
export const request = async(options = {}, withToken = true) => {
let token = wx.getStorageSync('token')
if (!token && withToken) {
const { code } = await new Promise((success, fail) => wx.login({ success, fail }))
token = await request({ url: '/user/token', data: { code }}, false)
wx.setStorageSync('token', token)
}
return new Promise((success, fail) => {
wx.request({
headers: { ...(options.headers || {}), ...(token ? { token } : {}) },
...options, success, fail
})
})
}
request({ url: '/user/info' })
request({ url: '/user/rule' })
如此每个接口都通过公用请求方法,并带上token值。可惜事与愿违,以上代码有个致命的问题
以下内容才是这篇文章想说的
上述代码真实运行起来时,在token存在的情况下正常运行。
在token过期或者不存在的情况下,存在并发问题。
如token不存在时,将异步调用获取token的接口,获取到后缓存起来,下次请求不需要重新请求。在只有1个请求时,这里没问题,但如果有多个请求并行加载时,token将会被刷新多次。会导致只有最后一个接口能正常运行,前面完成的获取token的接口将返回token失效这样的信息。
解决方法也很简单
// 公用请求
let getTokenQueue = null
export const request = async(options = {}, withToken = true) => {
return new Promise(async(success, fail) => {
let token = ''
if (withToken) {
token = await (getTokenQueue || (getTokenQueue = getToken()))
}
wx.request({
headers: { ...(options.headers || {}), ...(token ? { token } : {}) },
...options, success, fail
})
})
}
const getToken = async() => {
let token = wx.getStorageSync('token')
if (!token) {
const { code } = await new Promise((success, fail) => wx.login({ success, fail }))
token = await request({ url: '/user/token', data: { code }}, false)
}
return token
}
request({ url: '/user/info' })
request({ url: '/user/rule' })
问题是解决了,但是类似的需求在许多地方都有。那就封装一下吧
// 合并请求
export function mergeStep(wrapped) {
let request = null;
return function(...args) {
const that = this;
if (request) return request;
request = wrapped.apply(that, args);
if (Object.prototype.toString.call(request) === "[object Promise]") {
request.finally(() => {
request = null;
});
}
return request;
};
}
第一次发文章,主要是为了蹭矿石,代码未经实测,主要是提供个思路,见谅。