4

I'm a bit stuck on the middle portion of my code. I know what I want the end to look like, and the beginning, but cannot seem to fill in the blank.

I call fetch on 4 API endpoints, and store the Promises into a relevant key that tells me what they are.

Then I loop through all of those using a for in loop, and push all those Promises into an array so I can call Promise.all on them.

After successfully logging the array to the console, I see now that the array is filled with objects with my data. However, there is no way for me to tell which data belongs to what object key, like in the original object.

Is there a better way to do this? I do know for a fact that I want to use Promise.all here in this code; that's something I don't want to budge on, because I am trying to figure out how I can make this possible without giving up (been at this for a couple hours now).

At the end of my code (this is just pseudocode for a real life example in my React app), I just want to take the final object, and push it into state.

Any help is appreciated.

//imitating React State as an example
const state = {
  iliakan: '',
  remy: '',
  jeresig: '',
}

//put all of the urls in an object with a key-pair value to describe the data
const githubAPI = {
  iliakan: 'https://api.github.com/users/iliakan',
  remy: 'https://api.github.com/users/remy',
  jeresig: 'https://api.github.com/users/jeresig'
}

//create an empty object to assign promises to keys
const movieData = {};
const promiseArr = [];
//store promise into relevant key
for (const user in githubAPI) {
  movieData[user] = fetch().then(res => res.json())
}
//now movieData has keys with values set to related Promises
console.log(movieData);
//loop through these promises, and put them in an Array for Promise.all
for (const userData in movieData) {
  promiseArr.push(movieData[userData])
}

//Use Promise.all on all of those promises
Promise.all(promiseArr).then(responseArr => console.log(responseArr);

//this is where I am stuck. I now have an array of objects with all the correct data, but I don't know how to reassign them back to their original, matching key that they had in the movieData object!


//end goal is to have an object like this
//const movieData = {
//  iliakan: {//object with data from api},
//  remy: {//object with data from api},
//  jeresig: {//object with data from api}
//}

//use the movieData to setState and update current component state

3 Answers3

6

A way to do this, is by seeing the "connection between a key and a property" as it's own "thing" in programming: a key-value pair. The javascript API calls them "entries", and uses a simple 2 element array as the "thing": ['key', 'value'] would be key: 'value'.

You can go from an object to entries with Object.entries(the_object). It will return an array with "entry-array"-s inside it:

const githubAPI = {
  iliakan: 'https://api.github.com/users/iliakan',
  remy: 'https://api.github.com/users/remy',
  jeresig: 'https://api.github.com/users/jeresig'
}

let githubEntries = Object.entries(githubAPI)
// githubEntries = [
//   ['iliakan', 'https://api.github.com/users/iliakan'],
//   ['remy', 'https://api.github.com/users/remy'],
//   ['jeresig', 'https://api.github.com/users/jeresig'],
// ]

Now you can use this concept, and make the promises also "entries", the result and the key combined in a [key, value] array. This way, you can later rebuild the object from the entries in the Promise.all result.

let promiseOfEntries = Promise.all(Object.entries(githubAPI).map((entry) => {
  let [key, value] = entry;
  return fetch().then(x => x.json()).then(result => {
    // Important step here: We combine the result with the key,
    // into a new [key, value] entry
    return [key, result];
  })
}

let promiseOfResults = promiseOfEntries.then(resultEntries => {
  // From the "entries", we build an object again here
  let results = {};
  for (let [key, value] of resultEntries) {
    results[key] = value;
  }
  return results;
});

promiseOfResults.then(results => {
  // Do anything with results!
  console.log('results:', results);
});

Lodash even has _.fromPairs to do this [ [key, value], [key2, value2], ...] to { key: value, key2: value2 } conversion. https://lodash.com/docs/4.17.11#fromPairs

You can use this concept of "entries" for any place where you juggle between objects and arrays.

Hope I explained it well enough, don't be afraid to ask questions in the comments if I haven't!

Michiel Dral
  • 3,932
  • 1
  • 19
  • 21
  • I actually do have a question (still going over the code you wrote line-by-line); on this line `return fetch().then(x => x.json()).then(result => {`, I should be filling in the `fetch()` like `fetch(value)`, correct? – Phillip Choi Feb 26 '19 at 00:16
  • @PhillipChoi yes! You can fill in any fetch arguments there, as you as you keep the `return [key, result];` to keep the result and the key connected – Michiel Dral Feb 26 '19 at 13:19
  • Such a creative approach that would've never crossed my mine. Thank you Michiel! – Phillip Choi Feb 27 '19 at 03:50
3

You simply need to map the keys with the matching array indices. This is all you're missing:

const orderOfKeys = [] // create a helper Array

for (const userData in movieData) {
  promiseArr.push(movieData[userData])
  orderOfKeys.push(userData) // Memoize the keys, now the index matches the proper key
}

//Use Promise.all on all of those promises
Promise.all(promiseArr).then(responseArr => {
  const movieData = {}

  for (let i = 0; i < responseArr.length; i++) {
    movieData[orderOfKeys[i]] = responseArr[i]
  }

  console.log(movieData) // bingo
});

Here's a one liner to replace the for loop:

const movieData = responseArr.reduce((mD, response, index) => ({ ...mD, [orderOfKeys[index]]: response }), {})
Ben Steward
  • 2,338
  • 1
  • 13
  • 23
1

Try the following, which makes a key array that will track the order of the promises and allow you to know which "user" is associated with each promise. You can then set the movieData object correctly.

const movieData = {};
// Added keyTracker array declaration here...
const promiseArr = [], keyTracker = [];
for (const user in githubAPI) {
  movieData[user] = fetch().then(res => res.json())
}
console.log(movieData);
for (const user in movieData) {
  // Track the order of the promises by saving the key in our keyTracker
  keyTracker.push(user);
  promiseArr.push(movieData[user]);
}

Promise.all(promiseArr).then(responseArr => console.log(responseArr));

// Now we have a lookup for the keys (keyTracker) that will allow us to 
// set the movieData correctly.
keyTracker.forEach((key, idx) => {
  movieData[key] = promiseArr[idx];
});
Julian
  • 1,078
  • 5
  • 17