解决微信小程序 onLaunch 和 onLoad 执行顺序问题的一种方法

856 阅读2分钟

介绍

在微信小程序开发中,通常在 App.js 文件的 onLaunch 方法中获取登录凭证(以下称为:token),然后在 Page.js 文件的 onLoad 方法中执行页面数据请求。由于获取 token 和页面数据请求都是异步的,控制这两个方法的执行顺序可能会有困难,常常导致在页面请求数据时无法获取到 token。

本文介绍了如何利用事件广播和请求拦截器来实现对获取 token 和页面数据请求的顺序控制,而且无需在每个页面都进行操作。

实现逻辑

实现代码

event.js

//事件广播
const eventListeners = new Map();
const registerEventListener = (eventName, callback) => {
    if (!eventListeners.has(eventName)) {
        eventListeners.set(eventName, []);
    }
    eventListeners.get(eventName).push(callback);
};

const emitEvent = (eventName, data) => {
    const listeners = eventListeners.get(eventName);
    if (listeners) {
        listeners.forEach(listener => {
            listener(data);
        });
    }
};

module.exports = {
    emitEvent,
    registerEventListener
  }

Token.js

//TOKEN操作封装

import { emitEvent }  from './event' //广播事件

/**
 * 设置 token
 * @param {object} tokenData - 包含 accessToken 和 expires 的对象。
 * @param {string} tokenData.accessToken - 访问令牌。
 * @param {string} tokenData.expires - 令牌过期时间。
 */
export const setToken = async ({ accessToken, expires }) => {
    if (!accessToken || !expires) return;

    const token = JSON.stringify({ accessToken, expires });
  
    try {
        //使用同步操作
        await wx.setStorageSync('token', token);
        emitEvent('setTokenOk');  //广播setTokenOk事件
    } catch (error) {
        console.error('设置 token 时出错:', error);
    }
};

/**
 * 获取 token
 * @returns {Promise<string|null>} - 如果令牌存在且未过期,则返回访问令牌;否则返回 null。
 */
export const getToken = () => {
    try {
        const value = wx.getStorageSync('token');
        if (!value) return Promise.resolve(null);

        const userToken = JSON.parse(value);
        const nowDate = Date.now();

        if (userToken.expires < nowDate) return Promise.resolve(null);

        return Promise.resolve(userToken.accessToken);
    } catch (error) {
        console.error('获取 token 时出错:', error);
        return Promise.resolve(null);
    }
};

request.js

import { getToken } from './token'; 
import { registerEventListener } from './event'  //注册事件

const BASE_URL = "https://api.com"

//拦截器
async function interceptor(url, data = {}, method = 'GET') {
    const token = await getToken();
    const whiteUrlList = ['/getWxOpenId']; //此处替换成获取的实际接口
    if (whiteUrlList.includes(url)) {
        return request(url, data, method);
    } else if (token) {
        return request(url, data, method, token);
    } else {
        // 当没有 token 时,等待 setTokenOk 事件触发后重新发起请求
        return new Promise(resolve => {
            //注册 setTokenOk 事件
            registerEventListener('setTokenOk', async () => {
                const newToken = await getToken();
                resolve(request(url, data, method, newToken));
            });
        });
    }
}

//实际请求
function request(url, data = {}, method = 'GET', token = '') {
  
    return new Promise((resolve, reject) => {
        wx.request({
            url: BASE_URL + url,
            data,
            method,
            timeout: 5000,
            header: {
                "x-token": token,
            },
            success: res => {

                resolve(res.data);
            },
            fail: err => {
                reject(err);
            }
        })
    })
}

export const post = (url, data = {}) => interceptor(url, data, 'POST');

App.js

import { setToken, getToken } from "./utils/token"
import { post } from './utils/request'
App({
  async onLaunch() {
    await this.checkToken()
  },
  async checkToken() {
    const token = await getToken()
    if (!token) {
      await wx.login({
        success: async (res) => {
            try {
              const { code, data } = await post('/getWxOpenId', { code: res.code })
              if (code === 0) {
                setToken(data) 
              }
            } catch (error) {
                console.log(error);
            }
        
        }
      })
    }
  },
})