处理Promise并发

142 阅读2分钟

不知道掘友们平时是否有遇到过这种场景

在微信小程序加载首页时,同时发送多个请求,如:

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;
  };
}

第一次发文章,主要是为了蹭矿石,代码未经实测,主要是提供个思路,见谅。