How We Handle Data Requests and Cancellations in Wope
In Wope, we ditch the overhead of complex query libraries and directly send requests when components mount. While data is being retrieved, a loading screen keeps users informed. However, to prevent unnecessary requests and maintain efficiency, we’ve implemented a simple solution called Request Pool
.
Request Pool
Whenever we send a GET request within a component or scope, we simply wrap it with the collect
method. This method works behind the scenes by creating a special signal and attaching it to the dispatched requests. If a request ever needs to be cancelled (because plans change!), this signal swoops in and gracefully stops it, preventing unnecessary data from being fetched for users.
A Practical Example: Using Request Pool for Get Request
Here’s a concrete example demonstrating how to leverage Request Pool
in your Vue components:
import { ref } from "vue";
import axios from "axios";
import { useRequestPool } from "./requestPool";
const requestPool = useRequestPool();
const loading = ref(false);
const ourData = ref([]);
async function sendRequest() {
try {
const { data } = await requestPool.collect("key", () => {
loading.value = true;
ourData.value = [];
return axios.get("https://example.com");
});
ourData.value = data;
} catch (error) {
if (axios.isCancel(error)) {
console.log("Request is canceled by request pool");
}
} finally {
loading.value = false;
}
}
You might be wondering why we set the loading
value to true
within the collect
callback instead of before using the method. There’s a reason for this! Request Pool
can’t predict cancellations before you call collect
. To keep things running synchronously, we run our side effects within the callback.
Request Pool
also cancels all requests when the component is unmounted. This ensures that no data is fetched when it’s no longer needed.
Sending multiple requests with Request Pool
Because the collect
method is synchronous, sending multiple requests at once isn’t possible. To achieve this, we use the Promise.all
method:
const [first, second] = await requestPool.collect("key", () => {
loading.value = true;
return Promise.all([
axios.get("https://example.com/1"),
axios.get("https://example.com/2"),
]);
});
Under the Hood: How Request Pool Works
When you utilize the collect
method, Request Pool
modifies a global variable named currentSignal
. Additionally, a custom Axios interceptor is in place to retrieve and attach this signal to the actual request before it’s sent out.
A Peek at the Custom Axios Interceptor:
import axios from "axios";
import { getCurrentSignal } from "./requestPool";
axios.interceptors.request.use((config) => {
const signal = getCurrentSignal();
if (signal) {
config.signal = signal;
}
return config;
});
Request Pool Implementation:
import { onScopeDispose, getCurrentScope } from "vue";
let currentSignal: AbortSignal | null = null;
export function getCurrentSignal() {
return currentSignal;
}
export function useRequestPool() {
const pool = new Map<string, AbortController>();
function collect<T extends Promise<any>>(key: string, callback: () => T): T {
pool.get(key)?.abort();
const controller = new AbortController();
currentSignal = controller.signal;
const returnValue = callback();
currentSignal = null;
pool.set(key, controller);
return returnValue.finally(() => {
pool.delete(key);
});
}
function cancelAll() {
for (const controller of pool.values()) {
controller.abort();
}
pool.clear();
}
function cancel(key: string) {
const controller = pool.get(key);
if (controller) {
controller.abort();
pool.delete(key);
}
}
if (getCurrentScope()) onScopeDispose(cancelAll);
return {
collect,
cancel,
cancelAll,
};
}
We hope this explanation provides a clear understanding of how Request Pool
simplifies data request management and races in Wope!