You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让第二个Pinia插件等待第一个插件执行完成?

解决方案

方法一:合并插件,强制顺序执行

Pinia 默认不会等待异步插件执行完成再加载下一个,可将两个插件包装成一个异步插件,先完成认证逻辑,再初始化 API:

// 创建合并插件
import { authenticationPlugin } from './authentication';
import { apiPlugin } from './api';
import type { PiniaPluginContext } from 'pinia';

export async function combinedPlugin(context: PiniaPluginContext) {
  // 等待认证逻辑执行完成
  await authenticationPlugin(context);
  // 再执行API初始化
  await apiPlugin(context);
}

在注册 Pinia 时使用该合并插件:

// main.ts
import { createPinia } from 'pinia';
import { combinedPlugin } from './plugins/combined';

const pinia = createPinia();
pinia.use(combinedPlugin);

此方式确保认证插件的异步操作完成后,API 插件才启动,初始化 API 实例时 store.idToken 已存在有效值。

方法二:修改API插件,延迟初始化API实例

保留两个独立插件,调整 API 插件逻辑,仅在 idToken 存在时创建 API 实例,同时订阅 idToken 变化更新 API:

// 修改后的apiPlugin.ts
export async function apiPlugin({ store }: PiniaPluginContext) {
  const msalInstance = store.msalInstance as PublicClientApplication;

  // 封装API实例创建逻辑
  const createApi = (token: string) => {
    const api = new Api(new Configuration({
      basePath: config.BASE_PATH,
      accessToken: token
    }));

    // 调整拦截器逻辑,自动处理token过期
    api.axios.interceptors.request.use(async (config) => {
      const tokenResponse = await msalInstance.acquireTokenSilent(store.msalRequest);
      
      if (tokenResponse.expiresOn!.getTime() < Date.now()) {
        const { idToken } = await msalInstance.acquireTokenSilent(store.msalRequest);
        store.idToken = idToken;
        // 更新store中的API实例
        store.api = createApi(idToken);
        config.headers.Authorization = `Bearer ${idToken}`;
      } else {
        config.headers.Authorization = `Bearer ${tokenResponse.idToken}`;
      }

      return config;
    });

    return api;
  };

  // 订阅idToken变化,更新API实例
  store.$subscribe((mutation) => {
    if (mutation.events.key === 'idToken' && mutation.events.newValue) {
      store.api = createApi(mutation.events.newValue);
    }
  });

  // 初始化时若idToken已存在,直接创建API
  if (store.idToken) {
    store.api = createApi(store.idToken);
  }
}

调用 API 的组件中,需确保 API 实例就绪后再执行请求:

<script setup>
import { useStore } from '@/stores/yourStore';
import { watch } from 'vue';

const store = useStore();

// 监听API实例状态,就绪后调用接口
watch(() => store.api, (api) => {
  if (api) {
    api.yourApiMethod().then(res => {
      // 处理响应数据
    });
  }
}, { immediate: true });
</script>

方法三:在Store内部处理初始化逻辑

放弃使用插件,直接在 Pinia Store 的 action 中处理认证和 API 初始化,通过控制执行顺序保证依赖关系:

// stores/main.ts
import { defineStore } from 'pinia';
import { PublicClientApplication, InteractionRequiredAuthError } from '@azure/msal-browser';
import { Api, Configuration } from '@/api';
import config from '@/api/config';

export const useMainStore = defineStore('main', {
  state: () => ({
    msalInstance: new PublicClientApplication({
      // 你的MSAL配置参数
    }),
    msalRequest: {
      // 你的MSAL请求配置
    },
    idToken: null as string | null,
    api: null as Api | null,
  }),
  actions: {
    async initAuth() {
      try {
        const { idToken } = await this.msalInstance.ssoSilent(this.msalRequest);
        const account = this.msalInstance.getAllAccounts()[0];
        this.msalInstance.setActiveAccount(account);
        this.idToken = idToken;
      } catch (error) {
        if (error instanceof InteractionRequiredAuthError) {
          const { idToken } = await this.msalInstance.acquireTokenPopup(this.msalRequest);
          this.idToken = idToken;
        }
      }
    },
    initApi() {
      if (!this.idToken) return;
      this.api = new Api(new Configuration({
        basePath: config.BASE_PATH,
        accessToken: this.idToken,
      }));

      this.api.axios.interceptors.request.use(async (config) => {
        const tokenResponse = await this.msalInstance.acquireTokenSilent(this.msalRequest);
        
        if (tokenResponse.expiresOn!.getTime() < Date.now()) {
          const { idToken } = await this.msalInstance.acquireTokenSilent(this.msalRequest);
          this.idToken = idToken;
          this.initApi();
          config.headers.Authorization = `Bearer ${idToken}`;
        } else {
          config.headers.Authorization = `Bearer ${tokenResponse.idToken}`;
        }

        return config;
      });
    }
  }
});

应用启动时,先完成认证再初始化 API:

// main.ts
import { createApp } from 'vue';
import { useMainStore } from './stores/main';
import App from './App.vue';

async function bootstrap() {
  const app = createApp(App);
  const store = useMainStore();
  
  // 先执行认证逻辑
  await store.initAuth();
  // 再初始化API实例
  store.initApi();
  
  app.mount('#app');
}

bootstrap();

此方式确保应用挂载前已完成认证和 API 初始化,组件渲染时可直接使用带有效 token 的 API 实例。


内容的提问来源于stack exchange,提问作者Ricardo de Vries

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 07:15:55