Actualmente tengo muchas llamadas simultáneas idénticas a mi backend, que difieren solo en un campo de ID:
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); } Esto es un desperdicio ya que hago más llamadas. Me gustaría mantener mis llamadas getData() , pero luego agregarlas en una sola llamada getDatas() a mi backend, luego devolver todos los resultados a las personas que llaman. Tengo más control sobre mi backend que el marco de la interfaz de usuario, por lo que puedo agregar fácilmente una llamada getDatas() en él. La pregunta es cómo "muxear" las llamadas JS en una llamada de back-end, "demuxear" el resultado en las promesas de la persona que llama.
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 ??? } En Java, crearía un Map<int, CompletableFuture> y, al finalizar mi solicitud de back-end, buscaría el CompletableFuture y llamaría complete(data) en él. Pero creo que en JS Promise s no se pueden crear sin pasar un resultado explícito.
¿Puedo hacer esto en JS con Promises?
No está claro cuál es su objetivo final. Me imagino que podría pasar por sus llamadas según sea necesario; Tal vez algo como:
for (let x in cache){ if (x.has(id)) return x; } //OR for (let x=0; x<id.length;x++){ getData(id[x]) }Podría funcionar. Es posible que pueda agregar un método de sincronización a la mezcla si es necesario.
No estoy seguro de en qué consiste su backend, pero sé que GraphQL es un buen sistema para realizar múltiples llamadas.
En última instancia, puede ser mejor manejarlos todos en una sola solicitud, en lugar de múltiples llamadas.
El caché puede ser un identificador de asignación de objeto regular para prometer funciones de resolución y la promesa a la que pertenecen.
// cache maps ids to { resolve, reject, promise, requested } // resolve and reject belong to the promise, requested is a bool for bookkeeping const cache = {};Es posible que deba disparar solo una vez, pero aquí sugiero setInterval para verificar regularmente el caché en busca de solicitudes no resueltas:
// keep the return value, and stop polling with clearInterval() // if you really only need one batch, change setInterval to setTimeout function startGetBatch() { return setInterval(getBatch, 100); } La lógica empresarial solo llama a getData() que solo entrega (y almacena en caché) promesas, como esta:
function getData(id) { if (cache[id]) return cache[id].promise; cache[id] = {}; const promise = new Promise((resolve, reject) => { Object.assign(cache[id], { resolve, reject }); }); cache[id].promise = promise; cache[id].requested = false; return cache[id].promise; }Al guardar la promesa junto con el resolutor y el rechazador, también estamos implementando el caché, ya que la promesa resuelta proporcionará lo que resolvió a través de su método then().
getBatch() le pide al servidor en un lote los ID de getData() aún no solicitados e invoca las funciones de resolución/rechazo correspondientes:
function getBatch() { // for any const ids = []; Object.keys(cache).forEach(id => { if (!cache[id].requested) { cache[id].requested = true; ids.push(id); } }); return backend.getDatas(ids).then(datas => { Object.keys(datas).forEach(id => { cache[id].resolve(datas[id]); }) }).catch(error => { Object.keys(datas).forEach(id => { cache[id].reject(error); delete cache[id]; // so we can retry }) }) }El lado de la persona que llama se ve así:
// start polling const interval = startGetBatch(); // in the business logic getData(5).then(result => console.log('the result of 5 is:', result)); getData(6).then(result => console.log('the result of 6 is:', result)); // sometime later... getData(5).then(result => { // if the promise for an id has resolved, then-ing it still works, resolving again to the -- now cached -- result console.log('the result of 5 is:', result) }); // later, whenever we're done // (no need for this if you change setInterval to setTimeout) clearInterval(interval);Creo que he encontrado una solución:
interface PromiseContainer { resolve; reject; } const requests: Map<number, PromiseContainer<Data>> = new Map(); let timeout: number | null = null; function getData(id: number) { const promise = new Promise<Data>((resolve, reject) => requests.set(id, { resolve, reject })) if (timeout == null) { timeout = setTimeout(() => { backend.getDatas([...requests.keys()]).then(datas => { for (let [id, data] of Object.entries(datas)) { requests.get(Number(id)).resolve(data) requests.delete(Number(id)) } }).catch(e => { Object.values(requests).map(promise => promise.reject(e)) }) timeout = null }, 100) } return promise; } La clave fue descubrir que podía extraer (resolve, reject) de una promesa, almacenarlos, luego recuperarlos y llamarlos más tarde.