Search code examples
javascripttypescriptasynchronousasync-awaites6-promise

How to approach multiple async calls on single array?


this is bit more theoretical question. I originally intented to call this question Is it possible to iterate over map twice, but just from the sound of it, it sounds like an anti-pattern. So I assume I'm just approaching this wrong.

Also note: Data here servers as an abstraction. I know that what I'm doing here with data provided here is unnecessary, but please don't get fixated too much on data-structure and what-not. It does not represent the real (more complex, which furthermore is provided by client and I can't alter) data I'm working with. Instead approach the problem as how to return structured async calls for each array item please! :-)


My problem boils down to this:

  • I have array of ids on which I need to execture two separate asynchronous calls
  • Both of these callls need to pass (and in all id instances)

So as an example, imagine I have these two data-sets:

const dataMessages = [
  { id: "a", value: "hello" }, 
  { id: "b", value: "world" }
];

const dataNames = [
  { id: "a", name: "Samuel" },
  { id: "b", name: "John" },
  { id: "c", name: "Gustav"},
];

And an API-call mock-up:

const fetchFor = async (collection: Object[], id: string): Promise<Object> => {
  const user = collection.find(user => user.id === id);
  if (!user) {
    throw Error(`User ${id} not found`);
  }
  return user;
};

Now I need to call the fetchFor() function for both the data-sets, presumably inside the inside the Promise.all, given forEach is not asynchronous from a predetermined array of ids.

I was thinking something akin to maping a list of Promises for the Promise.all to execute. This works fine, when you only need to map a single api-call:

const run = async () => {
  const result = await Promise.all(
    ['a', 'b'].map(id => fetchFor(dataMessages, id)
  )
  console.log(result) // [{id: 'a', value: 'hello'}, {id: 'b', value: 'world}]
}

However I somehow need to return both promises for the

  • fetchFor(dataMessages, id)
  • fetchFor(dataNames, id)

inside the Promise.all array of Promises.


I guess I could always simply do a flatMap of two maps for both instances of API calls, but that sounds kinda dumb, given

  • I'd be doing array.map on same array twice
  • My data structure would not be logically connected (two separate array items for the same user, which would not even by followed by eachother)

So ideally I'd like to return dat in form of

 const result = await Promise.all([...])
 console.log(result)
 /* [
 *   {id: 'a', message: 'hello', name: 'Samuel'},
 *   {id: 'b', message: 'world', name: 'John'},
 *  ]

Or do I simply have to do flatmap of promises and then do data-merging to objects based on id identifier inside a separate handler on the resolved Promise.all?

I've provided a working example of the single-api-call mockup here, so you don't have to copy-paste.

Edit zen-wildflower-480ig

What would be the correct / common way of approaching such an issue?


Solution

  • You could nest Promise.all calls:

    const [messages, names] = await Promise.all([
      Promise.all(
        ['a', 'b'].map(id => fetchFor(dataMessages, id)
      ),
      Promise.all(
        ['a', 'b', 'c'].map(id => fetchFor(dataNames, id)
      )
    ]);
    

    If you're wanting to then merge the results after retrieved, it's just a matter of standard data manipulation.