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) => {
return setTimeout(() => fetchApi(api, fulltext, []), 100);
//return 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"
2
Answers
Hopefully I understood the issue you’re having.
I find that asynchronous functions may still interfere with each other when running on the same document — especially when modifying elements. Although they’re running at the same time, each line of code still takes a certain amount of time to execute (especially
console.log()
). They may be running at the same time, but they’re also running on the same main thread more or less.I ran into this issue when trying to implement loading symbols or progress bars. My loading symbols stuttered and my progress bars went from 0%-100% without any other steps.
The solution I found was Web Workers. Click here for a detailed guide for the Web Worker API.
Using a worker allows you to send logic to an entirely separate thread from the main thread — freeing up the UI to be modified and interacted with without being interrupted.
Here is some code modified from what I use to create and run a simple Worker with code from the same file:
Here is a simplified version of the code above
You could use a Worker in aggragateSearch() or fetchAPI() to offload the intense requirements of the fetch() function to a new thread, then you can update the div you hope to change in the Worker’s ‘message’ eventListener.
For instance, your fetchApi function might look something like this (untested):
If you can put your code in a real .js file, that might be cleaner than making a URL Object when using Workers.
This solution may not meet your need if I misunderstood your issue, — and you would need to adapt it to your full code, — but hopefully this is useful to you!
It looks like you want
loadCart
to finish its asychronous parts — ending with an update of thecartCounter
— before theaggregateSearch
fetch is initiated, as that is what yoursetTimeout
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 areturn
at two spots in your codeOr, alternatively, write that function as an
async
function, just like you did at other places in your code: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 theawait
operator on thatPromise.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 haveaggregateSearch
return a promise that only resolves when the asynchronous tasks have completed:Finally, await the returned promises in your event handler:
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 ofagggregateSearch
. So it is good practice to already put thatawait
operator here.