问题:

在app.vue 的 onLaunch 中做静默登录,同时首页pages/index/index onLoad 中请求核心业务接口,但经常出现 首页接口比登录接口先完后的情况----导致首页接口因缺少登录态 token 请求失败,用户进入首页后看不到数据 

问题本质:

微信小程序中,app.vue 的onLaunch 和首页的onLoad 是并行执行的, 而非顺序执行,静默登录是异步操作(调用wx.login,后端接口 ),需要一定时间,而首页onLoad 中的接口请求也是异步操作,且执行时机可能比登录接口更快,此时本地还没有存储token,首页接口携带空token 请求,自然会失败

方案:

方案1:最简单直接——首页 onLoad 中延迟请求(适合快速调试/简单项目)

方案2:   利用 Promise 等待登录完成(推荐,适配大多数项目)

  • 步骤1:app.vue 封装登录 Promise
  • 步骤2:首页等待登录 Promise 完成
     
    // pages/index/index.vue 优化后
    Page({
      async onLoad() {
        // 等待app.vue的登录Promise完成
        const loginSuccess = await getApp().loginPromise;
        if (loginSuccess) {
          // 登录成功,请求首页接口
          this.getHomeData();
        } else {
          // 登录失败,提示用户
          wx.showToast({ title: '登录失败,请重试', icon: 'none' });
        }
      },
      methods: {
        async getHomeData() {
          const token = wx.getStorageSync('token');
          const res = await wx.request({
            url: 'https://xxx.com/api/home',
            method: 'GET',
            header: { 'Authorization': `Bearer ${token}` }
          });
          // 后续逻辑...
        }
      }
    })

方案3: uniapp 小程序(Vue3) 专属方案 —— 基于全局Promise 控制onLaunch 与onLoad 执行顺序(main.js + app.vue 配置)

核心是在main.js 全局挂载Promise实例,app.vue的onLaunch 完成异步登录后触发Promise 的 resolve,所有页面onLoad 通过await 等待该Promise,确保登录完成后再执行接口请求,与方案2的局部Promise不同,它全局适配所有页面,贴合Vue3语法(通过getCurrentInstance 获取 proxy访问全局属性), 仅适配uni-app Vue3,不兼容Vue2 

1. 修改 main.js,全局挂载 Promise(关键配置)
// main.js(uni-app 入口文件,Vue3版本)
import { createSSRApp } from 'vue'
import App from './App.vue'

export function createApp() {
  const app = createSSRApp(App)
  
  // 核心:全局挂载Promise实例,用于控制onLaunch与onLoad的执行顺序
  app.config.globalProperties.$onLaunched = new Promise(resolve => {
    // 挂载resolve方法,供app.vue的onLaunch调用,标记onLaunch执行完成
    app.config.globalProperties.$isResolve = resolve
  })
  
  return { app }
}
2.修改 app.vue(Vue3 语法),onLaunch 中触发 resolve
// app.vue(uni-app,Vue3 语法,setup 语法糖)
<script setup>
import { getCurrentInstance, onLaunch } from "vue"
// 获取全局实例 proxy(Vue3 中获取全局属性的方式)
const { proxy } = getCurrentInstance()

// 静默登录方法(异步)
const silentLogin = async () => {
  try {
    // 1. 获取微信/uni 登录 code(uni 多端兼容写法)
    const { code } = await uni.login({ provider: 'weixin' })
    // 2. 调用后端静默登录接口,获取 token
    const [err, res] = await uni.request({
      url: 'https://xxx.com/api/silentLogin',
      method: 'POST',
      data: { code }
    })
    // 3. 存储 token(uni 多端兼容存储)
    if (res?.data?.code === 200) {
      uni.setStorageSync('token', res.data.data.token)
    }
  } catch (err) {
    console.error('静默登录失败:', err)
    uni.showToast({ title: '登录失败,请重试', icon: 'none' })
  }
}

// onLaunch 生命周期(Vue3 setup 语法写法)
onLaunch(async () => {
  // 1. 执行异步静默登录
  await silentLogin()
  // 2. 关键:登录完成后,触发全局 Promise resolve
  // 通知所有页面:onLaunch 已执行完毕,可以执行 onLoad 了
  proxy.$isResolve()
})
</script>
3. 首页 (Vue3语法), onLoad 中等待全局 Promise 完成

首页onLoad中,先通过await 等待全局挂载的$onLaunched Promise完成(即onLaunch中所有异步操作执行完毕),再请求业务接口,确保能获取到有效的登录态(token)

// pages/index/index.vue(uni-app,Vue3 语法,setup 语法糖)
<script setup>
import { getCurrentInstance, onLoad } from "vue"
const { proxy } = getCurrentInstance()

// 首页核心接口请求
const getHomeData = async () => {
  // 获取存储的 token
  const token = uni.getStorageSync('token')
  // 兜底:防止登录失败导致 token 为空
  if (!token) {
    uni.showToast({ title: '登录失败,无法加载数据', icon: 'none' })
    return
  }
  // 调用首页接口(uni 多端兼容请求)
  const [err, res] = await uni.request({
    url: 'https://xxx.com/api/home',
    method: 'GET',
    header: {
      'Authorization': `Bearer ${token}`
    }
  })
  if (res?.data?.code === 200) {
    // 处理首页数据
    console.log('首页数据:', res.data.data)
  } else {
    uni.showToast({ title: '数据加载失败', icon: 'none' })
  }
}

// 页面 onLoad 生命周期
onLoad(async () => {
  // 关键:等待全局 Promise 完成(等待 onLaunch 执行完毕)
  await proxy.$onLaunched
  // 此时 onLaunch 已执行完,登录态已生成,请求首页接口
  getHomeData()
})
</script>

关键说明

  1. 核心原理: main.js 中挂载的$onLaunched 是一个未resolve 的Promise,页面onLoad中await该Promise 时,会阻塞onLoad的后续逻辑; 直到app.vue的onLaunch中执行proxy.$isResolve(),Promise状态变为完成,页面onLoad 才会继续执行
  2. Vue3 语法重点: getCurrentInstance( ) 用于获取当前组件实例,proxy 是实例的代理对象,通过proxy可以访问到app.config.globalProperties 上挂载的全局属性($onLaunched,$isResolve)
  3. 与方案2的区别: 方案2是在app.vue 中定义局部Promise (loginPromise), 而该方案是通过main.js 全局挂载Promise,适配所有页面,无需在每个页面单独关联app 的Promise,更适合页面的uni小程序项目

方案4: 最优雅——全局拦截器 + 登录状态管理(适合中大型项目)

核心逻辑: 封装全局请求拦截器,所有接口请求前先判断是否有token; 若无token,等待登录完成后再发起请求; 若登录失败,统一处理(如跳转登录页),这种方式适合多页面,多接口的中大型项目,可统一管理登录态和接口请求

  • 封装全局请求(utils/request.js)

  • (若用方案2,保留 loginPromise;若用方案3,保留 onLaunch 返回 Promise,拦截器均可适配)

  • 首页使用全局请求

总结 :  

  • 简单项目/快速调试 -> 方案1 
  • 大多数常规项目 - > 方案2 
  • uni-app Vue3项目 -> 方案3 
  • 中大型多页面项目 => 方案4 : 统一管理,可扩展性强,适合长期维护

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐