Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

214
Views
Aggregate multiple calls then separate result with Promise

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 Promises can't be created without an explicit result being passed in.

Can I do this in JS with Promises?

about 4 years ago · Santiago Trujillo
3 answers
Answer question

0

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.

about 4 years ago · Santiago Trujillo Report

0

The cache can be a regular object mapping ids to promise resolution functions and the promise to which they belong.

// cache maps ids to { resolve, reject, promise, requested }
// resolve and reject belong to the promise, requested is a bool for bookkeeping
const cache = {};

You might need to fire only once, but here I suggest setInterval to regularly check the cache for unresolved requests:

// 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);
}

The business logic calls only getData() which just hands out (and caches) promises, like this:

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;
}

By saving the promise along with the resolver and rejecter, we're also implementing the cache, since the resolved promise will provide the thing it resolved to via its then() method.

getBatch() asks the server in a batch for the not-yet-requested getData() ids, and invokes the corresponding resolve/reject functions:

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
    })
  })
}

The caller side looks like this:

// 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);
about 4 years ago · Santiago Trujillo Report

0

I think I've found a solution:

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;
}

The key was figuring out I could extract the (resolve, reject) from a promise, store them, then retrieve and call them later.

about 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!