usage of promise with while loop results in freezing ui

Viewed 688

I am trying to replace async/await with promise (while preserving the logic/purpose/outcome of the code) but struggling to understand the problem resulting upon usage of promise. When I attempt to execute new version of the code using promise browsers freezes and seems to be executing infinite loop. Could you please help me to understand what the problem is?

Here is the working state of the code using async/await

async fetchNewJokes() {
let newJokes = [];

while (newJokes.length < 10) {
  const {data} = await   axios.get('https://icanhazdadjoke.com/', {
    headers: {
      Accept: 'application/json',
    },
  });
  newJokes.push(data.joke);
 
}

this.setState({
    jokes: newJokes,
  });

}

Here is the attempted replacement of async await with promise

fetchNewJokes() {
let newJokes = [];

while (newJokes.length < 10) {
  axios.get('https://icanhazdadjoke.com/', {
    headers: {
      Accept: 'application/json',
    },
  }).then({data}=>{
  newJokes.push(data.joke);
 })
  
}
this.setState({
    jokes: newJokes,
  })
}
2 Answers

First of all we have to understand that JS is single threaded. Just one thread processes the code of your program. It can run only one piece of the code (lets say function) at a time. So keeping this fact in mind lets try to find out whats going on in your code.

async fetchNewJokes() {
    let newJokes = [];

    while (newJokes.length < 10) {
        /*1*/ const {data} = await axios.get('https://icanhazdadjoke.com/', {
          headers: {Accept: 'application/json'}});
        /*2*/newJokes.push(data.joke);
        this.setState({jokes: newJokes});
    }
}

JS performs this function until it meets await on line \*1*\. Then it executes axios.get() and returns to the main code without waiting of result of axios.get().

Further it continues working with other code doing other useful things like rendering, processing user input and so on. At time to time it checks whether axios.get() finished or not.

When axios.get() finished it assign result to data value and continues performing this function from line \*2*\. On the next iteration of while loop it meets await again and repeat actions described above. And the same happens for every step of while loop.

Now lets look at the second code snippet.

fetchNewJokes() {
    let newJokes = [];

    while (newJokes.length < 10) {
        axios.get('https://icanhazdadjoke.com/', {
          headers: {Accept: 'application/json'}}).then(()=>{
            newJokes.push(data.joke);
            this.setState({jokes: newJokes});
        });
    }
}

JS performs this function and executes axios.get(). Now to execute .then() JS has to check at time to time whether axios.get() finished or not. But it can not!

JS thread is busy by performing while loop. You don't give it a time to do anything else. It continues to call axios.get() again and again without any possibility to check result. Furthermore since JS busy by doing the loop it doesn't have time for rendering. Loop consumes more and more resources. That's why browser freezes.

So what a solution? @gdh already gave you one of them. But if you need to make less changes to your code, just don't rely on result of Promise in the condition of the loop.

fetchNewJokes() {
    for (let i=0; i<10; i++) {
        axios.get('https://icanhazdadjoke.com/', {
          headers: {Accept: 'application/json'}}).then(()=>{
            this.setState({jokes: this.state.jokes.concat(data.joke)});
        });
    }
}

In this case the function executes axios.get() ten times and returns control to the main code.

Generally, don't set state inside while loop unless you need to. Your async/await code worked coz the state updates are batched and the state is updated when the enclosing scope execution is finished (which is while loop).

In your promise code the state is set in then block. So the component is re-rendered after each api call and seems like fetchNewJokes again and so on.

So use Promise.all to solve the issue.

Like this

const fetchNewJokes = () => {
  let promiseArr = [];
  const counter = 0;

  while (counter < 10) {
    promiseArr.push(
      axios
        .get("https://icanhazdadjoke.com/", {
          headers: {
            Accept: "application/json",
          },
        })
        .then((data) => {
          //   newJokes.push(data.joke);
          return data.joke;
        })
    );
    counter++;
  }
  Promise.all(promiseArr).then((newJokes) => {
    this.setState({
      jokes: newJokes,
    });
  });
};

Related