0

I have an async function making an api request. Once completed, it calls a different async function making an api request 4 times (intended to go in the order it's called). But every time it runs, those api requests return data in a random order.

First, I call fetchSearch, this works as expected.

    const fetchSearch = async () => {
        var myHeaders = new Headers();
        myHeaders.append("x-app-id", "...");
        myHeaders.append("x-app-key", "...");
        
        var requestOptions = {
        method: 'GET',
        headers: myHeaders,
        redirect: 'follow'
        };
        
        await fetch(`https://trackapi.nutritionix.com/v2/search/instant?query=${search}`, requestOptions)
        .then(response => response.text())
        .then(
            result => (
                handleSearch(JSON.parse(result).common)
            )
        )
        .catch(error => console.log('error', error));
    }

This calls handleSearch. I'm probably doing something wrong here.

    const handleSearch = (data) => {
        const dataList=[]
        for (var key in data) {
            if (data.hasOwnProperty(key)) {
                dataList.push(data[key])
            }
        }
        if (dataList[0] !== undefined) {
            setSearchResults([dataList[0], dataList[1], dataList[2], dataList[3]])
            fetchNutrition(dataList[0].food_name)
                .then(() => {fetchNutrition(dataList[1].food_name)})
                .then(() => {fetchNutrition(dataList[2].food_name)})
                .then(() => {fetchNutrition(dataList[3].food_name)})
        } else {
            setSearchError(true)
        }
    }

handleSearch calls fetchNutrition:

    const fetchNutrition = async (foodName) => {
        var h = new Headers();
        h.append("accept", "application/json");
        h.append("x-app-id", "...");
        h.append("x-app-key", "...");
        h.append("x-remote-user-id", "1");
        h.append("Content-Type", "application/json");

        var requestOptions = {
            method: 'POST',
            headers: h,
            body: JSON.stringify({ "query": foodName }),
            redirect: 'follow'
        }

        await fetch("https://trackapi.nutritionix.com/v2/natural/nutrients", requestOptions)
            .then(response => response.text())
            .then(result => {setNutritionResults(nutritionResults => [...nutritionResults, JSON.parse(result)])})
            .catch(error => console.log('error', error))
    }

With an array of 4 strings from fetchSearch, handleSearch and fetchNutrition should be going through each string and adding the corresponding nutrition JSON string to the nutritionResults array state (in the correct order in the array).

Every time it runs, all the nutrition results are returned in a random order in the nutritionResults array, and I'm assuming it's because handleSearch isn't calling the functions in the correct order. Or I'm missing another issue.

For example, if fetchSearch returns ["apple", "apples", "apple juice", "apple pie"], nutritionResults will end up as an array of length 4 but in a random order every time it runs.

3
  • 4
    for a start, handleSearch will start the fetchNutrition and keep executing without waiting for it, since, you don't wait for it - also, mixing async/await with .then/.catch is never a good idea Commented May 18, 2022 at 3:14
  • 1
    fetch() requests complete when they complete. You cannot rely on any particular order. Commented May 18, 2022 at 3:17
  • 1
    not a real issue, but .then(response => response.text()).then(result => ..... JSON.parse(result) ...) ... why not use response.json() then you won't need to JSON.parse the result Commented May 18, 2022 at 3:17

2 Answers 2

1

The issue is handleSearch() is not awaiting fetchNutrition(). Therefore the code continues without waiting - exactly as described.

To fix this all you need to do is wait for fetchNutrition() to complete:

const handleSearch = async (data) => {
    // ...

        await fetchNutrition(dataList[0].food_name)
            .then(() => {return fetchNutrition(dataList[1].food_name)})
            .then(() => {return fetchNutrition(dataList[2].food_name)})
            .then(() => {return fetchNutrition(dataList[3].food_name)})
    // ...
}

Note: The return in front of fetchNutrution() is important if you want await to wait for it to complete. Otherwise await will not wait for it because there is no Promise returned by the chain of .then().

Alternatively this does the exact same thing:

const handleSearch = (data) => {
    // ...

        return fetchNutrition(dataList[0].food_name)
            .then(() => {return fetchNutrition(dataList[1].food_name)})
            .then(() => {return fetchNutrition(dataList[2].food_name)})
            .then(() => {return fetchNutrition(dataList[3].food_name)})

    // NOTE: The bugfix is adding a "return" 
    // ...
}

Either of the above will cause handleSearch() to return a Promise which can be awaited.

Now you also need to allow fetchSearch() to await handleSearch(). Similar to the above you can add a return to do this:

    await fetch(`https://trackapi.nutritionix.com/v2/search/instant?query=${search}`, requestOptions)
    .then(response => response.text())
    .then(
        result => (
            return handleSearch(JSON.parse(result).common)

            // Note: The bugfix is adding the return above
            // so that this chain of .then() will return the
            // handleSearch() Promise which the above await
            // will wait for
        )
    )

Alternatively you could also do the following to allow fetchSearch() to wait for handleSearch():

    await fetch(`https://trackapi.nutritionix.com/v2/search/instant?query=${search}`, requestOptions)
    .then(response => response.text())
    .then(
        result => handleSearch(JSON.parse(result).common)
    )

    // Note: The bugfix is removing the braces "()" around
    // handleSearch() causing js to add an implicit "return"

Another alternative is to avoid mixing async/await with .then() chains altogether:

    const response = await fetch(`https://trackapi.nutritionix.com/v2/search/instant?query=${search}`, requestOptions)
    const result = await response.text()

    await handleSearch(JSON.parse(result).common)
Sign up to request clarification or add additional context in comments.

2 Comments

You also need to add returns to the then chain callbacks
@Bergi Well spotted
0

I think you use await is shorten than promise.then. Try this:

const handleSearch = async (data) => {
   ...
   await fetchNutrition(dataList[1].food_name)
   await fetchNutrition(dataList[2].food_name)
   await fetchNutrition(dataList[3].food_name)
   ...
}

2 Comments

Don't use nested .then(). The whole idea of Promises is that you can remove nesting. Just do flat .then() exactly the same as you would with await
@slebetman yeah, i will remove it

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.