Currently I have many concurrent identical calls to my backend, differing only on an ID field:
getData(1).then(...) // Each from a React component in a UI framework, so difficult to aggregate here
getData(2).then(...)
getData(3).then(...)
// creates n HTTP requests... inefficient
function getData(id: number): Promise<Data> {
return backend.getData(id);
}
This is wasteful as I make more calls. I'd like to keep my getData()
calls, but then aggregate them into a single getDatas()
call to my backend, then return all the results to the callers. I have more control over my backend than the UI framework, so I can easily add a getDatas()
call on it. The question is how to "mux" the JS calls into one backend call, the "demux" the result into the caller's promises.
const cache = Map<number, Promise<Data>>()
let requestedIds = []
let timeout = null;
// creates just 1 http request (per 100ms)... efficient!
function getData(id: number): Promise<Data> {
if (cache.has(id)) {
return cache;
}
requestedIds.push(id)
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas(requestedIds).then((datas: Data[]) => {
// TODO: somehow populate many different promises in cache??? but how?
requestedIds = []
timeout = null
}
}, 100)
}
return ???
}
In Java I would create a Map<int, CompletableFuture>
and upon finishing my backend request, I would look up the CompletableFuture and call complete(data)
on it. But I think in JS Promise
s can't be created without an explicit result being passed in.
Can I do this in JS with Promises?
CodePudding user response:
A little unclear on what your end goal looks like. I imagine you could loop through your calls as needed; Perhaps something like:
for (let x in cache){
if (x.has(id))
return x;
}
//OR
for (let x=0; x<id.length;x ){
getData(id[x])
}
Might work. You may be able to add a timing method into the mix if needed.
Not sure what your backend consists of, but I do know GraphQL is a good system for making multiple calls.
It may be ultimately better to handle them all in one request, rather than multiple calls.
CodePudding user response:
I think I've found a solution:
const data: Map<number, Promise<Data>> = new Map();
const requestedData: Map<number, PromiseContainer<Data>> = new Map();
let timeout = null;
function getData(id: number) {
if (data.has(id)) {
return data.get(id);
}
const promise = new Promise<Data>((resolve, reject) => requestedData.set(id, { resolve, reject }))
if (timeout == null) {
timeout = setTimeout(() => {
backend.getDatas([...requestedData.keys()]).then(datas => {
for (let [id, data] of Object.entries(datas)) {
requestedData[id].resolve(data)
}
requestedData.clear()
timeout = null
}).catch(e => {
Object.values(requestedData).map(promise => promise.reject(e)))
timeout = null
})
}, 100)
}
return promise;
}
The key was figuring out I could extract the (resolve, reject)
from a promise, store them, then retrieve and call them later.