3

I am looking at using Promise.all() to fetch data from multiple endpoints. With Promise.all() an array is returned and you can destructor the items from that array e.g.

const [fetch1, fetch2] = Promise.all(urls.map(url => fetch(url)))

I am looking for way to dynamically name those destructured items (name1 and name2) using a config object.

const urls = [
  {
    name: 'google',
    url: 'https://www.google.com'
  },
  {
    name: 'bbc',
    url: 'https://www.bbc.co.uk'
  }
]

const [google, bbc] = Promise.all(urls.map(url => fetch(url.url)))

In the above example items destructured from the promise.all should use the name from the urls array i.e. urls.name

Essentially I am looking for a way to name my exports in the config object that is passed to promise.all()

the main reason for this is that data returned from these promises you be keyed under the name specified in the urls config object. so we could also do

const data = Promise.all(urls.map(url => fetch(url.url)))
console.log(data.google || data.bbc)
chinds
  • 1,761
  • 4
  • 28
  • 54
  • 1
    This has less to do with `Promise.all` or destructuring than with [dynamically creating variables](https://stackoverflow.com/questions/35939289/how-to-destructure-into-dynamically-named-variables-in-es6) (or even "exports"?). [No, you cannot and should not do that](https://stackoverflow.com/q/31907970/1048572) (unless you meant to create an object). – Bergi Apr 28 '20 at 15:44
  • so you want a variable variable :) I dont believe thats possible. The only way I can think you can do that would be have an object, where the keys are the `url.name` e.g. 'google' and the value is the promise, but that would get messy – andy mccullough Apr 28 '20 at 15:44
  • 1
    How would that help? I mean, you're not going to know what the names are when you're writing code after the `Promise.all`, right? So you wouldn't be able to do `const [magic here] = Promise.all(...); google.blah(); bbc.yada();` because you have no guarantee the configuration mentions `google` or `bbc`. – Heretic Monkey Apr 28 '20 at 15:46
  • Are you willing to write a helper method? – WolverinDEV Apr 28 '20 at 15:46
  • @HereticMonkey other components that will use this retuned data will know the names, i.e they will expect to see google on that data object. – chinds Apr 28 '20 at 15:48
  • @WolverinDEV sure if it will help here. – chinds Apr 28 '20 at 15:48
  • As mentioned above, Promise.all work only with arrays. You can use `reduce` to turn that array into object, though – lavavrik Apr 28 '20 at 15:49

1 Answers1

0

This should do:

const fetchUrl = (url) => fetch(url.url).then((response) => ({ [url.name]: response }));

const mergeResults = (results) => results.reduce(
  (mergedResults, result) => ({ ...mergedResults, ...result }),
  {}
);

const data = Promise
  .all(urls.map(fetchUrl))
  .then(mergeResults)
  .then((data) => {
    console.log(data.google || data.bbc);
  });

Bergi's update:

const data = Promise
  .all(urls.map(fetchUrl)) // same as above
  .then((results) => Object.assign({}, ...results)) // elegance++
  .then((data) => {
    console.log(data.google || data.bbc);
  });
sp00m
  • 47,968
  • 31
  • 142
  • 252
  • Or you could do `response => ({[url.name]: response})` and then `mergeResults = results => Object.assign({}, ...results)` – Bergi Apr 28 '20 at 15:58
  • @Bergi Sweet, updated :) – sp00m Apr 28 '20 at 15:59
  • This works perfectly, please you explain what the mergeResults function is doing? i do not 100% understand that one. – chinds Apr 28 '20 at 16:05
  • @Bergi Oh wait, in `Object.assign({}, ...results)`, `results` would be an array? – sp00m Apr 28 '20 at 16:05
  • Ive added an `await` infront of the Promise.all() and removed the final `.then` this way I can simply return the data variable. – chinds Apr 28 '20 at 16:06
  • @chinds `mergeResults` takes an array of results, and reduces it. You can reduce to any type. Here, I'm reducing into an object. The first callback parameter will be called for each element on the array, it takes the "accumulated value" (i.e. the result of the previous iteration), and the current value, and returns the "next value". The second parameter is the initial value (also the final value if the array is empty). More details on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce. – sp00m Apr 28 '20 at 16:08
  • But @Bergi's solution might be even more elegant, where you'd build the final object by destructuring the resulting array directly, but not too sure I 100% understand it though. – sp00m Apr 28 '20 at 16:09
  • @sp00m Yes, `results` is still an array (that's what `Promise.all(…)` fulfills with), an array of single-key objects. The array is then spread to become multiple arguments to `Object.assign`. – Bergi Apr 28 '20 at 16:11
  • @Bergi Damn, yeah, brilliant, didn't think of it; @chinds, you can basically replace `.then(mergeResults)` by `.then((results) => Object.assign({}, ...results))` and get rid of the manually reduction. – sp00m Apr 28 '20 at 16:13
  • The linked duplicate has a lot of examples where the `all` or the promises are wrapped. So you don't have to have the intermediate boilerplate you have here. – user120242 Apr 28 '20 at 16:13
  • 1
    This is ace guys, thank you all. – chinds Apr 28 '20 at 16:19