小程序无感登入小花招

143 阅读2分钟

业务场景

前段时间在做企业微信小程序时,领导给了登入的要求:不要看到任何的弹框;也不要看到有页面重定向,工作台多个功能入口都可以直接进入;(我就呵呵)

稍微分析

  1. 由于是在企业微信所以员工进入之后匹配成功手机号就可以登入成功
  2. 工作台的功能都是属于同一个小程序,也意味着各个业务接口在请求之前都应检查登入状态
  3. 小程序并未像vue-router那样提供了路由拦截的机制

代码思路

先看看代码

    function _http (options) {
      const { url, data, header, method } = options
      return new Promise((resolve, reject) => {
        wx.request({
          url: url,
          data,
          method,
          header: Object.assign(
            {
              "Content-Type": "application/json",
            },
            header
          ),
          success: function (res) {
            const { data } = res
            resolve(data)
          },
          fail: function (response) {
            console.warn(response, "请求出错")
            reject(response)
          },
        })
      })
    }

    function _qy_login (data) {
      return request({
        url: "/login",
        method: "post",
        data,
      })
    }

    function _qy_get_other () {
      return request({
        url: "/other",
        method: "post",
        data,
      })
    }

    const WhiteList = ["/login"]
    let user = {}

    let _lockIsLoading = false
    let localResolve
    let localReject
    let localLock = new Promise((resolve, reject) => {
      localResolve = resolve
      localReject = reject
    })

    async function qy_login () {
      if (_lockIsLoading) {
        return
      }
      _lockIsLoading = true
      try {
        const { data: info } = await _qy_login()
        user = info
        localResolve(info.certificate)
      } catch (error) {
        console.warn("登入出错了", error)
        localReject(error)
      } finally {
        // 重新生成锁
        setTimeout(() => {
          _lockIsLoading = false
          localLock = new Promise((resolve, reject) => {
            localResolve = resolve
            localReject = reject
          })
        }, 0)
      }
    }

    async function request (options) {
      let { url, data, header, method } = options

      if (WhiteList.some((e) => url.match(e))) {
        console.log(url, "「白名单接口请求中!」")
      } else {
        if (user.certificate) {
          options.header.certificate = user.certificate
        } else {
          // 判断是否正在登入中
          if (_lockIsLoading) {
            try {
              const certificate = await localLock
              options.header.certificate = certificate
            } catch (error) {
              console.warn(`${url}:登入失败`)
              return Promise.reject(error)
            }
          } else {
            const info = await qy_login()
            options.header.certificate = info.certificate
          }
        }
      }

      return _http(options)
    }

第一个关键点

let localResolve
let localReject
let localLock = new Promise((resolve, reject) => {
  localResolve = resolve
  localReject = reject
})

还是在神奇的Promise上面想办法,大家都清楚new Promise(executor)中的executor是在宏任务中执行的而resolve, reject作为函数在js中属于引用类型。那么办法就来了,只要把这两哥们勾出来,那我想让localLock啥时候fulfilled啥时候rejected还不是手拿把掐。我们只需要在业务请求中检验一下是否登入,统一用一个Promise实例localLock在所有业务async请求中做拦截就行。至此大部分业务请求基本可以在page中直接发就行,不用再考虑是否登入的情况。为啥不是全部,那是因为假设业务接口中需要一个用户id的参数,那么这个接口的调用时机就要另外处理

结语

  1. 利用AOP思想做统一拦截。口子统一登入的逻辑自然清晰很多
  2. ecmascript函数是引用类型大家要牢记
  3. 依赖用户信息的业务接口需要特殊处理,比如observer监听全局用户信息,有了信息才发请求
  4. 在重置localLock的时候为啥用setTimeout?有没有其他的重置方法?留给大家讨论