Home > Blockchain >  JS async functions execute in random order
JS async functions execute in random order

Time:05-18

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.

CodePudding user response:

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(() => {fetchNutrition(dataList[1].food_name)})
            .then(() => {fetchNutrition(dataList[2].food_name)})
            .then(() => {fetchNutrition(dataList[3].food_name)})
    // ...
}

Alternatively this does the exact same thing:

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

        return fetchNutrition(dataList[0].food_name)
            .then(() => {fetchNutrition(dataList[1].food_name)})
            .then(() => {fetchNutrition(dataList[2].food_name)})
            .then(() => {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)

CodePudding user response:

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)
   ...
}
  • Related