在 Vue 3 中使用 Pinia 时,如果你希望在多个地方同时调用同一个远程数据获取方法时,只有第一次调用真正执行远程请求,而后续的调用等待第一次请求的结果并共享该结果,你可以通过以下方式实现:
Promise
和缓存机制你可以在 Pinia store 中维护一个 Promise
对象,用于缓存远程请求的结果。当第一次调用时,发起请求并将 Promise
存储起来。后续的调用直接返回这个 Promise
,从而实现只发起一次请求的效果。
假设你有一个 useUserStore
的 Pinia store,用于获取用户信息:
import { defineStore } from 'pinia';
import { ref } from 'vue';
import axios from 'axios';
export const useUserStore = defineStore('user', () => {
const userData = ref(null);
let fetchPromise = null;
const fetchUserData = async () => {
if (fetchPromise) {
// 如果已经有正在进行的请求,直接返回该 Promise
return fetchPromise;
}
// 发起远程请求
fetchPromise = axios.get('/api/user')
.then(response => {
userData.value = response.data;
return userData.value;
})
.finally(() => {
// 请求完成后重置 fetchPromise
fetchPromise = null;
});
return fetchPromise;
};
return {
userData,
fetchUserData,
};
});
在组件中使用 fetchUserData
方法时,无论调用多少次,只有第一次会真正发起请求,后续的调用会等待第一次请求的结果:
Loading...
{{ userData }}
fetchPromise
: 用于存储当前正在进行的请求的 Promise
对象。如果 fetchPromise
已经存在,说明已经有请求在进行中,直接返回该 Promise
。
finally
: 在请求完成后(无论成功或失败),重置 fetchPromise
为 null
,以便下次调用时可以重新发起请求。
如果请求失败,fetchPromise
也会被重置,以便下次调用时可以重新发起请求。
如果你希望在一定时间内缓存数据,可以结合 setTimeout
或其他缓存机制来实现。
通过这种方式,你可以确保在多个地方同时调用远程数据获取方法时,只有第一次调用会真正发起请求,后续的调用会等待第一次请求的结果并共享该结果。
如果你使用的是 Option Store 而不是 Setup Store,写法会稍有不同,但核心逻辑是一样的。你可以在 state
中定义数据,在 actions
中定义方法,并通过一个 Promise
缓存机制来实现多次调用时只发起一次请求的效果。
以下是基于 Option Store 的实现方式:
import { defineStore } from 'pinia';
import axios from 'axios';
export const useUserStore = defineStore('user', {
state: () => ({
userData: null, // 存储用户数据
fetchPromise: null, // 存储当前的请求 Promise
}),
actions: {
async fetchUserData() {
// 如果已经有正在进行的请求,直接返回该 Promise
if (this.fetchPromise) {
return this.fetchPromise;
}
// 发起远程请求
this.fetchPromise = axios.get('/api/user')
.then(response => {
this.userData = response.data; // 更新 state 中的数据
return this.userData;
})
.finally(() => {
// 请求完成后重置 fetchPromise
this.fetchPromise = null;
});
return this.fetchPromise;
},
},
});
在组件中使用时,逻辑与之前类似:
Loading...
{{ userData }}
state
:
userData
: 用于存储从远程获取的用户数据。
fetchPromise
: 用于存储当前正在进行的请求的 Promise
对象。
actions
:
fetchUserData
: 这是一个异步方法,用于获取用户数据。
如果 fetchPromise
已经存在(即有请求正在进行),直接返回该 Promise
。
如果 fetchPromise
为 null
,则发起新的请求,并将 Promise
存储到 fetchPromise
中。
请求完成后,无论成功还是失败,都会在 finally
中重置 fetchPromise
为 null
,以便下次调用时可以重新发起请求。
组件中使用:
在 onMounted
钩子中调用 fetchUserData
方法。
通过 await
等待请求完成,并更新 loading
状态。
错误处理:
如果请求失败,fetchPromise
也会被重置,以便下次调用时可以重新发起请求。
你可以在 catch
中处理错误逻辑。
缓存机制:
如果你希望在一定时间内缓存数据,可以在 state
中添加一个 lastFetchTime
字段,并在 fetchUserData
中检查是否在缓存时间内。如果是,则直接返回缓存的数据。
export const useUserStore = defineStore('user', {
state: () => ({
userData: null,
fetchPromise: null,
lastFetchTime: null, // 上次请求的时间
}),
actions: {
async fetchUserData() {
const now = Date.now();
const cacheDuration = 5 * 60 * 1000; // 缓存 5 分钟
// 如果缓存未过期,直接返回缓存的数据
if (this.lastFetchTime && now - this.lastFetchTime < cacheDuration) {
return this.userData;
}
// 如果已经有正在进行的请求,直接返回该 Promise
if (this.fetchPromise) {
return this.fetchPromise;
}
// 发起远程请求
this.fetchPromise = axios.get('/api/user')
.then(response => {
this.userData = response.data;
this.lastFetchTime = now; // 更新上次请求时间
return this.userData;
})
.finally(() => {
this.fetchPromise = null;
});
return this.fetchPromise;
},
},
});
通过这种方式,你可以在 Option Store 中实现多次调用时只发起一次请求的效果,并且可以灵活地扩展缓存机制。
如果你的 fetchUserData
需要请求多个接口,并且每个接口都已经封装好了异步函数(可以直接用 await
调用),那么你可以通过以下方式实现:
确保多个接口的请求是并发的:使用 Promise.all
来同时发起多个请求。
缓存机制:仍然使用一个 Promise
来缓存整个请求过程,确保多次调用时只发起一次请求。
以下是基于 Option Store 的实现方式:
假设你有两个异步函数 fetchUserInfo
和 fetchUserOrders
,分别用于获取用户信息和用户订单:
import { defineStore } from 'pinia';
// 假设这是封装好的异步函数
async function fetchUserInfo() {
const response = await fetch('/api/user/info');
return response.json();
}
async function fetchUserOrders() {
const response = await fetch('/api/user/orders');
return response.json();
}
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null, // 存储用户信息
userOrders: null, // 存储用户订单
fetchPromise: null, // 存储当前的请求 Promise
}),
actions: {
async fetchUserData() {
// 如果已经有正在进行的请求,直接返回该 Promise
if (this.fetchPromise) {
return this.fetchPromise;
}
// 发起多个请求
this.fetchPromise = Promise.all([fetchUserInfo(), fetchUserOrders()])
.then(([userInfo, userOrders]) => {
// 更新 state 中的数据
this.userInfo = userInfo;
this.userOrders = userOrders;
return { userInfo, userOrders }; // 返回结果
})
.finally(() => {
// 请求完成后重置 fetchPromise
this.fetchPromise = null;
});
return this.fetchPromise;
},
},
});
在组件中使用时,逻辑与之前类似:
Loading...
User Info
{{ userInfo }}
User Orders
{{ userOrders }}
fetchUserData
方法:
使用 Promise.all
并发请求多个接口(fetchUserInfo
和 fetchUserOrders
)。
将结果分别存储到 state
中的 userInfo
和 userOrders
。
返回一个 Promise
,确保多次调用时只发起一次请求。
缓存机制:
使用 fetchPromise
缓存当前的请求 Promise
。
如果 fetchPromise
已经存在,直接返回该 Promise
,避免重复请求。
组件中使用:
在 onMounted
钩子中调用 fetchUserData
方法。
通过 await
等待请求完成,并更新 loading
状态。
如果你的 fetchUserData
需要根据参数动态请求不同的数据,可以将参数传递给 fetchUserData
,并在缓存时考虑参数的影响。
例如:
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
userOrders: null,
fetchPromises: new Map(), // 使用 Map 缓存不同参数的请求
}),
actions: {
async fetchUserData(userId) {
// 如果已经有相同参数的请求,直接返回该 Promise
if (this.fetchPromises.has(userId)) {
return this.fetchPromises.get(userId);
}
// 发起多个请求
const promise = Promise.all([fetchUserInfo(userId), fetchUserOrders(userId)])
.then(([userInfo, userOrders]) => {
this.userInfo = userInfo;
this.userOrders = userOrders;
return { userInfo, userOrders };
})
.finally(() => {
// 请求完成后从缓存中移除
this.fetchPromises.delete(userId);
});
// 缓存当前请求
this.fetchPromises.set(userId, promise);
return promise;
},
},
});
在组件中使用时,可以传递参数:
onMounted(async () => {
try {
await userStore.fetchUserData(123); // 传递 userId
} catch (error) {
console.error('Failed to fetch user data:', error);
} finally {
loading.value = false;
}
});
使用 Promise.all
可以并发请求多个接口。
通过 fetchPromise
缓存机制,确保多次调用时只发起一次请求。
如果需要支持动态参数,可以使用 Map
来缓存不同参数的请求。
这种方式既高效又灵活,适合复杂的异步数据获取场景。