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"
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.