javascriptasynchronouspromise

Promise all is blocking other fetch call


I actually consume some remote API using the FETCH API like below

document.addEventListener("DOMContentLoaded", () => {
    loadCart();
    aggregateSearch("france", ["api1", "api2"]);
});

const aggregateSearch = async (term, AvailableApis) => {
    console.log("aggregateSearch")
    await Promise.all(AvailableApis.map((api) => {
        fetchApi(api, term);
    }))
};

const fetchApi = async (api, term) => {
    console.log("fetchApi");
    const aggregateRawResponse = await fetch('aggregate.php', { method: 'POST', body: JSON.stringify({ source: api, term: term }) });
    const aggregateResponse = await aggregateRawResponse.json();

    console.log('response from API done');

    document.getElementById('myDiv' + api)?.innerHTML = aggregateResponse.formattedResponse;
}

const updateCartCount = async () => {
    console.log("update cartcount"); // this line is executed
    const fetchNbPhotos = await fetch("count.php");
    const data = await fetchNbPhotos.json();
    console.log("cart count done");
    document.getElementById('cartCounter').innerHTML = parseInt(data["nb"]); // this is executed once aggregateSearch done
}

const loadCart = () => {
    console.log("start loading cart");
    fetch('cart.php')
    .then((response) => {
        return response.text();
    })
    .then((html) => {
        console.log("loading cart is done updating cart count");
        document.getElementById('cart')?.innerHTML = html
        updateCartCount();
    });
}

Here the console output

start loading cart
aggregateSearch
fetchApi
loading cart is done updating cart count
update cartcount
cart count done
response from API done

The part that retrieves the contents of the APIs works perfectly, but the part that load my cart into cart div and updating my cartCounter div is only update once aggregateSearch has finished.

I tried to make loadCart async/await but without success

How can I make aggregateSearch non-blocking and update my cartCounter div while fetching API is pending ?

EDIT

I tried to change the aggregateSearch like below

const aggregateSearch = async (term, AvailableApis) => {
    console.log("aggregateSearch")
    await Promise.all(AvailableApis.map((api) => {
        setTimeout(() => fetchApi(api, fulltext, []), 100);
        //fetchApi(api, fulltext, []);
    }))
};

And my cartCounter div id updated instantly

EDIT2

Here is the reproductible example https://jsfiddle.net/3sdLweag/26/

Console output

"start loading cart"
"aggregateSearch"
"update cartcount"
"response from API done"
"cart count done"

As fetchApi has 2000 delay, loadCart and updateCartCount have 100 delay both expected output should be

"start loading cart"
"aggregateSearch"
"update cartcount"
"cart count done"
"response from API done"

Solution

  • It looks like you want loadCart to finish its asychronous parts -- ending with an update of the cartCounter -- before the aggregateSearch fetch is initiated, as that is what your setTimeout version will likely achieve.

    Then the solution is to await the asynchronous tasks made by loadCart.

    First of all loadCart should return a promise. It needs a return at two spots in your code

    const loadCart = () => {
        console.log("start loading cart");
        return fetch('cart.php')  // <-- return the promise!
        .then((response) => {
            return response.text();
        })
        .then((html) => {
            console.log("loading cart is done updating cart count");
            document.getElementById('cart')?.innerHTML = html;
            return updateCartCount(); // <-- return the promise!
        });
    }
    

    Or, alternatively, write that function as an async function, just like you did at other places in your code:

    const loadCart = async () => { // <-- make it async
        console.log("start loading cart");
        const response = await fetch('cart.php');  // <-- and use await
        const html = await response.text();        // <-- 
        console.log("loading cart is done updating cart count");
        document.getElementById('cart')?.innerHTML = html;
        return updateCartCount(); // <-- return the promise!
    }
    

    Another point is that you don't get a useful result from Promise.all, as you don't pass it an array of promises. This makes the await operator on that Promise.all quite useless. It has no negative effect in your case, as you never use that result, but it would make more sense to do it in the right way, and have aggregateSearch return a promise that only resolves when the asynchronous tasks have completed:

    const aggregateSearch = async (term, AvailableApis) => {
        console.log("aggregateSearch");
        await Promise.all(AvailableApis.map((api) => {
            return fetchApi(api, term);  // <-- return the promise!
        }));
    };
    

    Finally, await the returned promises in your event handler:

    document.addEventListener("DOMContentLoaded", async () => {  // <-- make it async
        await loadCart(); // <-- await it
        await aggregateSearch("france", ["api1", "api2"]); // <-- await it
    });
    

    NB: that last await is not really needed, but it could become necessary when you decide to add more code in that event handler, which relies on the effects of agggregateSearch. So it is good practice to already put that await operator here.