De-duplicate parallel reading operations using beforeRequest hook: how to store Response promise? #672
-
|
(This is just a small side quest to satiate my curiosity and not a critical issue I need to solve or a problem with ky. I'm basically just looking for ideas.) I'm working on an application that triggers a fairly large number of requests to REST APIs returning data in JSON format used to populate things in the UI. In some cases, I "over-fetch" data because I'd otherwise have a cumbersome data flow between otherwise independent trees of UI components. A good example is a user's profile: for its data, we Custom approach wrapped around kyI realized that for reading operations like this, the network requests could be de-duplicated. For any subsequent identical (i.e. has the same reading (GET or HEAD) HTTP method and the same URL) reading operation, I avoid the network request and instead use the Simplified version of my codeimport ky, { type KyResponse } from 'ky'
const readingOperations = new Map<string, Promise<KyResponse>>()
async function request(
path: string,
options: Parameters<typeof ky>[1] = {},
) {
options.method = options.method ? options.method.toUpperCase() : 'GET'
const operation = `${options.method}:${path}`
let response: KyResponse;
try {
let promise: Promise<KyResponse>
const readingOperation = readingOperations.get(operation)
if (readingOperation === undefined) {
promise = ky(path, options)
if (['GET', 'HEAD'].includes(options.method)) {
readingOperations.set(operation, promise)
}
} else {
promise = readingOperation
}
response = await promise
} catch (error) {
throw error
} finally {
readingOperations.delete(operation)
}
return await response.clone().json()
}
const [data1, data2, data3] = await Promise.all([
request('users/1'),
request('users/1'), // should be deduplicated
request('users/1'), // should be deduplicated
])
assertValueEqualness(data1, data2, data3)One caveat with this approach is that I have to A real issue with this approach is that I compute the map key using request paths and not fully resolved URLs. That's a potential bug as I might miss request parameters from an ky-centered approach using a
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
|
The (untested) const pendingRequests = new Map();
const api = ky.extend({
hooks: {
beforeRequest: [
async request => {
const key = `${request.method}:${request.url}`;
// if theres already a pending request, return its promise
if (pendingRequests.has(key)) {
const response = await pendingRequests.get(key);
// clone the response since it can only be read once
return response.clone();
}
// otherwise, store the pending request promise
const responsePromise = fetch(request).then(response => {
pendingRequests.delete(key);
return response;
}).catch(error => {
pendingRequests.delete(key);
throw error;
});
pendingRequests.set(key, responsePromise);
// return the response to fulfill the request
return await responsePromise;
}
]
}
}); |
Beta Was this translation helpful? Give feedback.
The
beforeRequesthook can return aResponseto skip the actual network request entirely. heres how to deduplicate parallel requests:(untested)