1

I am working with ReactJS and need to create an array to insert data, store it in localStorage, and view it later. I first create the array using this.subscriptionsList = []; I then use this method to retrieve the data.

  fetchNow = async (token, nextToken) => {
      fetch("somewebsite")
        .then(( unformattedData ) => {
          return unformattedData.json();
        })
        .then(( webData) => {
          for(let i = 0; i < webData.length; i++){
            this.subscriptionsList.push("someData");
          }
          if(webData.hasOwnProperty('token')){
            this.fetchNow(token, webData.token);
          }else{
            return;
          }
        })
      }

The function is not complete as it is not the focal point of my question. It works fine and pushes the correct data.

I retrieve the data using this method:

this.fetchNow(response.accessToken, "")
      .then( () => {
        console.log(this.subscriptionsList);
        console.log(JSON.stringify(this.subscriptionsList));
        localStorage.setItem("subscriptionsList", JSON.stringify(this.subscriptionsList));
        //console.log(JSON.parse(localStorage.getItem("subscriptionsList")));
      })

The problem appears when I try to use JSON.stringify. Doing console.log(this.subscriptionsList) prints an array object with all the data inside of it as expected. However when I go to do console.log(JSON.stringify(this.subscriptionsList)) it returns []. Further, when printing the object itself, the property length: 125 but when doing console.log(this.subscriptionsList.length) it returns 0 and when accessing using this.subscriptionsList[0] it returns undefined. I was reading the specifications and it appears as though JSON.stringify should work with a javascript array. Am I missing something react specific or is this just not available with JSON.parse?

https://gyazo.com/72aafe248ef61649a38c06d03fb3d830
https://gyazo.com/c2690b4392774fe4a98254a4d15f3a32
https://gyazo.com/e0793b5ec05da4259e16100f275da414
Jimmy
  • 85
  • 1
  • 2
  • 8

2 Answers2

1

You need to return the promise chain in fetchNow or you can't chain to it later as you're trying. You'll also have to return the recursive fetchNow called inside fetchNow if you want it to be chained as well:

fetchNow = (token, nextToken) => {
  return fetch("somewebsite")
    .then(( unformattedData ) => {
    return unformattedData.json();
  })
    .then(( webData) => {
    for(let i = 0; i < webData.length; i++){
      this.subscriptionsList.push("someData");
    }
    if(webData.hasOwnProperty('token')){
      return this.fetchNow(token, webData.token);
    }
  })
}

async functions do not magically make asynchronous operations synchronous. They (1) return Promises and (2) allow for the await keyword. But since you're not using await, there's no need for fetchNow to be async, and since the fetch chain is already a Promise, you can return it directly without async.

If you do want to use async and await, it would make the code much flatter and easier to understand:

fetchNow = async (token, nextToken) => {
  const response = await fetch("somewebsite");
  const webData = await response.json();
  for(let i = 0; i < webData.length; i++){
    this.subscriptionsList.push("someData");
  }
  if(webData.hasOwnProperty('token')){
    await this.fetchNow(token, webData.token);
  }
  // async function will automatically return a Promise that resolves when the end is reached
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • As per @Jimmy comment `console.log(this.subscriptionsList) prints an array object with all the data inside of it as expected`.So,it's not promise chain issue. – RIYAJ KHAN Jun 09 '18 at 05:03
  • That's due to counter-intuitive `console.log` implementation - it's logging the *live* object, not the object as it *was* when it was logged. – CertainPerformance Jun 09 '18 at 05:04
  • See here: https://stackoverflow.com/questions/24175017/google-chrome-console-log-inconsistency-with-objects-and-arrays (it's not limited to Chrome, many more questions on this issue as well) – CertainPerformance Jun 09 '18 at 05:08
  • I thought of this at first as well but when I go to print the object it is the complete array. Am I still missing something? – Jimmy Jun 09 '18 at 05:22
  • 1
    @Jimmy `console.log` unintuitively returns the *live* object, not the object at the moment it was logged. If you `stringify` the object beforehand, it'll more accurately represent the object's contents at that moment in time. – CertainPerformance Jun 09 '18 at 05:24
1

This is what's happening.... What console.log(JSON.stringify(something)) does is that it serialises the immediate value and prints the serialised reference.(in other words it creates a cloned copy and prints the cloned copy not the original reference it won't wait for hoisted/mutated value). What console.log(something) does is that it refers to the original value and prints the actual result(mutated/hoisted) rather the immediate result. As your method being an asynchronous one you can clearly observe this. You can wait until the asyc call is done then you can push the actual variable to the localStorage. (Note that even local storage stores as serialised values)

Edit 1:-

let onComplete = (subscriptionsList) => {
    console.log(JSON.stringify(subscriptionsList));
    localStorage.setItem("subscriptionsList", JSON.stringify(subscriptionsList));
    //console.log(JSON.parse(localStorage.getItem("subscriptionsList")));
}

fetchNow = async (token, nextToken, onComplete) => {
  fetch("somewebsite")
    .then(( unformattedData ) => {
      return unformattedData.json();
    })
    .then(( webData) => {
      for(let i = 0; i < webData.length; i++){
        this.subscriptionsList.push("someData");
      }
      if(webData.hasOwnProperty('token')){
        this.fetchNow(token, webData.token);
      }else{
        onComplete(this.subscriptionsList);
        return;
      }
    })
}

this.fetchNow(response.accessToken, "", onComplete)
  .then( () => {
    console.log("direct call ", this.subscriptionsList);
})
karthik
  • 1,100
  • 9
  • 21
  • I don't really understand what you are saying as I am not too familiar with hoisting and such in javascript. From what I understand you are saying that somehow the array becomes empty? This, however, isn't the case. The end result should have all of the data. I have included screenshots in my original post for more details – Jimmy Jun 09 '18 at 05:20
  • @Jimmy try the sample code in edit1 haven't tested it though. All you have to do is to wait for the async call to complete. – karthik Jun 09 '18 at 05:41
  • I see you and CertainPerformance were providing the same point but I believe his answer will help those in the future more as it demonstrates the importance of promise chaining so I accepted his answer. However, your answer does demystify the situation greatly and is also very important. – Jimmy Jun 09 '18 at 06:06