0

Some of my code is not behaving the way I would like it too. I'm making two API requests but they are not executed in order. I read a bit and tried doing async await but couldn't reach the expected result. What am I missing here?

useEffect(() => {
    const selectedSdksKeys = Object.keys(selectedSdks);

    fetch(
      `/api/sdk/churn?sdk1_id=${selectedSdksKeys[0]}&sdk2_id=${selectedSdksKeys[1]}&sdk3_id=${selectedSdksKeys[2]}`
    )
      .then((res) => res.json())
      .then((res) => setAllSdksInfo(res))

// I would like the below to happen only after the above has happened. How?

    selectedSdksKeys.forEach((id) => {
      fetch(`/api/sdk/app_count?sdk_id=${id}`)
        .then((res) => res.json())
        .then((res) => {
            setAllSdksInfo(prev => ({...prev, res}))
    });
    });
  }, [selectedSdks]);
3
  • await before the first fetch should do the trick Commented Feb 20, 2021 at 20:45
  • but I can't just stick it there; throws an error. what other adjustments? Commented Feb 20, 2021 at 20:48
  • Try useEffect(async () => Commented Feb 20, 2021 at 20:50

3 Answers 3

1

There are two ways you can solve this:

1- You are missing an await statement, but this will require for everything that is happening in their to be wrapped into an async function. Read more here

With your code, something like this should work:

useEffect(() => {
    (async () => {
        const selectedSdksKeys = Object.keys(selectedSdks);

            await fetch(
                `/api/sdk/churn?sdk1_id=${selectedSdksKeys[0]}&sdk2_id=${selectedSdksKeys[1]}&sdk3_id=${selectedSdksKeys[2]}`
            )
                .then((res) => res.json())
                .then((res) => setAllSdksInfo(res))

            // Code that must happen after:
            await Promise.all (
                selectedSdksKeys.map((id) => {
                    return fetch(`/api/sdk/app_count?sdk_id=${id}`)
                        .then((res) => res.json())
                        .then((res) => {
                                setAllSdksInfo(prev => ({...prev, res}))
                    });
                });
            )
    })()
})

2- Alternatively, if you wish to skip that, you an just put the function you want to happen after the first in a then() statement. It should look something like this:

useEffect(() => {
    const selectedSdksKeys = Object.keys(selectedSdks);

    fetch(
      `/api/sdk/churn?sdk1_id=${selectedSdksKeys[0]}&sdk2_id=${selectedSdksKeys[1]}&sdk3_id=${selectedSdksKeys[2]}`
    )
      .then((res) => res.json())
      .then((res) => setAllSdksInfo(res))
      .then(() => {
         // Your second API call which was being run out of order:
         selectedSdksKeys.forEach((id) => {
           fetch(`/api/sdk/app_count?sdk_id=${id}`)
            .then((res) => res.json())
             .then((res) => {
                setAllSdksInfo(prev => ({...prev, res}))
          });
    });

      })
    
  }, [selectedSdks]);

Edit: I also noticed that you are doing a forEach with a fetch inside it. This will cause the fetches to be out of order. If this is a problem, consider wrapping it in a Promise.all with a .map instead of a foreach. Something like this:

// Your second API call which was being run out of order:
return Promise.all(selectedSdksKeys.nmap((id) => {
  return fetch(`/api/sdk/app_count?sdk_id=${id}`)
     .then((res) => res.json())
     .then((res) => {
        setAllSdksInfo(prev => ({...prev, res}))
     });
})

Sign up to request clarification or add additional context in comments.

Comments

1

You could add another state variable to act as a flag. You set a flag to true after first request is finished, and then check if the 1st request if finished before executing other one.

const [loaded, setLoaded] = useState(false);

useEffect(() => {
    const selectedSdksKeys = Object.keys(selectedSdks);
    if(!loaded){
      fetch(
        `/api/sdk/churn?sdk1_id=${selectedSdksKeys[0]}&sdk2_id=${selectedSdksKeys[1]}&sdk3_id=${selectedSdksKeys[2]}` )
        .then((res) => res.json())
        .then((res) => {
           setLoaded(true)
           setAllSdksInfo(res))
        }
     }

    if(loaded){
      selectedSdksKeys.forEach((id) => {
        fetch(`/api/sdk/app_count?sdk_id=${id}`)
          .then((res) => res.json())
          .then((res) => {
            setAllSdksInfo(prev => ({...prev, res}))
       }
      });
    });
  }, [selectedSdks,loaded]);

1 Comment

And the next time selectedSdks changes loaded will still be true, won't it?
1

I found two solutions on it:

  1. with for of loop:

    const sequentialRequest = async () => { const list = await getPokemonList(); // [{name:string, url:string}...] for (const listItem of list.results) { const pokemon = await getPokemon(listItem.url); console.log(pokemon.name); } };

  2. with reduce method:

    const sequentialRequest = async () => { const list = await getPokemonList(); // [{name:string, url:string}...] list.results.reduce(async (previousPromise, pokemon) => { await previousPromise; return (await getPokemon(pokemon.url)).name; }, Promise.resolve(undefined)); };

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.