小程序中如何正确的处理登录问题

小程序登录的请求应该写在哪里?App.js ?如何处理某些页面必须需要登录,而某些不需要。登录接口的异步问题。

问题描述

你一般会把 login 的请求放在哪里?大部分人都应该是在 app.js 中 onLaunch 中做登录处理,如果仅仅是这样做登录是会存在一个问题。login 接口一定是异步返回的,那么进入业务的页面如 index 页面也有请求,业务请求大部分需要在 login 接口返回后才能去请求。但是 onLaunch 是不能延迟加载。

那么问题就来了 index 页面如何在正确的时机去发送请求?有些页面因为是需要被分享出去的,是不用依赖登录,大部分是一定要 token 回来之后才能发送请求。

网上的一些解决方案

  1. emit 在登录完成后触发一个自定义事件通知到页面登录完成。index 页面监听这个事件。 弊端:如果有几个需要第一时间被加载的页面就要监听几次。维护困难
  2. 下载插件,网上有针对这种问题有部分写一些库例如: https://developers.weixin.qq.com/community/develop/article/doc/00082263c94fe0a3559381c876bc13 我觉得没必要因为这一小点然后就下载一个库,本着万一出现问题还没有无从排查。小程序这一块还是尽量少下库,毕竟本身这一块的生态并不健全,还是把风险控制到最小,比较安全。

我的解决方案

我是从封装 request 请求入手。

  1. App 里维护一个全局 loginPromise
  2. 所有业务请求前先 await ensureLogin()
  3. 首次会真正发登录;并发请求会等待同一个 Promise
  4. 登录完成后再放行业务请求

也可以根据自己需要加入白名单,如果不需要 token 的接口 例如分享到朋友圈的落地页,不需要登录就可以请求

// app.js
App({
  globalData: {
    token: '',
    loginPromise: null,
  },

  ensureLogin() {
    if (this.globalData.token) return Promise.resolve(this.globalData.token);
    if (this.globalData.loginPromise) return this.globalData.loginPromise;

    this.globalData.loginPromise = new Promise((resolve, reject) => {
      wx.login({
        success: ({ code }) => {
          wx.request({
            url: 'https://api.example.com/login',
            method: 'POST',
            data: { code },
            success: (res) => {
              const token = res.data.token;
              this.globalData.token = token;
              resolve(token);
            },
            fail: reject,
            complete: () => {
              this.globalData.loginPromise = null;
            },
          });
        },
        fail: (err) => {
          this.globalData.loginPromise = null;
          reject(err);
        },
      });
    });

    return this.globalData.loginPromise;
  },
});
// utils/request.js
const app = getApp();

function request(options) {
  return app.ensureLogin().then((token) => {
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,
        header: {
          ...(options.header || {}),
          Authorization: `Bearer ${token}`,
        },
        success: resolve,
        fail: reject,
      });
    });
  });
}

module.exports = { request };

使用 Hugo 构建
主题 StackJimmy 设计