0

I create an object and add properties and values to it. However, when I try to iterate through the keys it says I have a size of 0.

let hash = {};

this.props.user.user.following.forEach(async topicId => {
    await API.graphql(graphqlOperation(queries.getTopic, {id: topicId})).then(data => {
    if(data) {
        const tweetId = data.data.getTopic.post.id;
        if(!hash[tweetId]){
            let post = data.data.getTopic.post;
            post.topics = [{
                id: data.data.getTopic.id,
                name: data.data.getTopic.name
            }]
            hash[tweetId] = post;
        } else {
            console.log("Found tweet. Appending to topics array.");
            let post = hash[tweetId];
            let topicsArr = post.topics;
            topicsArr.push({
                id: data.data.getTopic.id,
                name: data.data.getTopic.name
            })
            post.topics = topicsArr;
            hash[tweetId] = post;
        }
    }})
});

console.log("Hash: ", hash);
console.log("Map size: ", Object.keys(hash).length);
let tweets = [];
for(var key in hash) {
    tweets.push(hash[key]);
}
console.log("Tweets to go into state: ", tweets);

My output is as follows(the array on the last line is empty):

Output

Dane B
  • 169
  • 12
  • 4
    Is that the actual code, seems not possible with what is provided. – epascarello Nov 20 '19 at 20:08
  • I omitted the for-loop to add the other object which is shown in the console. However the problem still applies regardless. – Dane B Nov 20 '19 at 20:14
  • 1
    If possible, please provide full example of the code (cause this is not working) – Drag13 Nov 20 '19 at 20:16
  • 1
    that blue [i] in the console is telling me no.... it is not the same code. Change the line `console.log("Hash: ", hash);` to `console.log("Hash: ", JSON.stringify(hash));` – epascarello Nov 20 '19 at 20:16
  • What do you see if you put in a breakpoint and step through? Does anything weird happen (or, more importantly, **not** happen) when this runs? – matthew-e-brown Nov 20 '19 at 20:21
  • 1
    Your object is empty when you log it and its size. It will be not empty after the promise resolves. That is why you can expand it in the browser console with its final properties. – giuseppedeponte Nov 20 '19 at 20:28
  • 2
    You can see it if you try to change your first log to `console.log("Hash: ", JSON.stringify(hash));` – giuseppedeponte Nov 20 '19 at 20:29
  • @giuseppedeponte you are right it shows empty when I do `console.log("Hash: ", JSON.stringify(hash))` How do I wait for the promise to resolve? Doesn't it already wait since I put `await` in front of my async call? – Dane B Nov 20 '19 at 20:40

1 Answers1

3

You're adding to the hash object with an async function.

So, each of runs through the forEach are done asynchronously, and the evaluation of the rest of the code continues on. The block where you use the data runs before your asynchronous functions have all completed.

You should have both the code where you fetch and the code where you process data from an API call in the same asynchronous block, since, when you think about it, a block of code can't depend on asynchronous code without itself being asynchronous.

You probably want to await all of the API calls you're doing in that loop. To do so, you can use Array.prototype.map() and Promise.all().

const tweets = async () => {
  let hash = {};

  await Promise.all(this.props.user.user.following.map(async topicID => {
    const data = await API.graphql(graphqlOperation(queries.getTopic, { id: topicID }));
    // No need to use .then(), since we're awaiting anyways
    if (data) {
      const tweedID = data.data.getTopic.post.id;
      if (!hash[tweetID]) {
        let post = data.data.getTopic.post;
        post.topics = [{
          id: data.data.getTopic.id,
          name: data.data.getTopic.name
        }]
        hash[tweedID] = post;
      } else {
        console.log("Found tweet. Appending to topics array.");
        let post = hash[tweedID];
        let topicsArr = post.topics;
        topicsArr.push({
          id: data.data.getTopic.id,
          name: data.data.getTopic.name
        });
        post.topics = topicsArr;
        hash[tweedID] = post;
      }
    }
  }));

  console.log("Hash: ", hash);
  console.log("Map size: ", Object.keys(hash).length);
  let tweets = [];
  for (const key in hash) {
    tweets.push(hash[key]);
  }
  console.log("Tweets to go into state: ", tweets);
}

tweets();

// Or, put the whole thing in brackets and call it immediately:
(async () => {
  // ...
})();

The reason that you see that the logged object has values is because of a feature of the dev-console that live-refreshes objects' properties, so they will no longer reflect the state when they've been logged, but instead display their current value.

Consider the following example:

const obj = {};
console.log(obj);
obj.foo = 'bar';

You'll see that the obj's foo property is 'bar', even though it was undefined when console.log was actually run. As @epascarello pointed out in the comments, you can tell when your browser has done this because of the little blue [i]. If you hover over it, you'll see that it says "This value was evaluated just now".

To avoid that behavior, JSON.stringify the object before logging:

const obj = {}
console.log(JSON.stringify(obj))
obj.foo = 'bar'

This will log {}, as you might expect, since it turns it into a string and spits that text out, instead of a reference to obj.

See more here.

matthew-e-brown
  • 2,837
  • 1
  • 10
  • 29
  • JavaScript code (except workers) runs on the main thread. And async code also runs in the main thread. While we are "awaiting", code bellow is not executing. – Drag13 Nov 20 '19 at 21:07