Contents
Overview
Displaying the progress of multiple tasks as they are completed can be helpful to the user as it indicates how long they may need to wait for the remaining tasks to finish.
We can accomplish this by incrementing a counter after each promise has resolved.
The video version of this tutorial can be found here...
Our desired output will look something like this, as the tasks are in progress.
Loading 7 out of 100 tasks
Implementation
Let's start with the markup!
All you need is a script tag to point to a JavaScript file (which will be implemented below), and one div element, whose text will be manipulated to update the progress counter of tasks completed.
<!DOCTYPE html>
<html>
<body>
<div id="progress"></div>
<script src="app.js"></script>
</body>
</html>
Next up, the JavaScript!
We will begin by creating a function which resolves a promise after a random time has passed.
We do this as it closely resembles how it will work in a real-world application, e.g. HTTP requests resolving at different times.
async function task() {
return new Promise(res => {
setTimeout(res, Math.random() * 5000);
})
}
Secondly, we will create an array of 100 promises and update the progress text to inform the user when all of the tasks have finished.
const loadingBar = document.getElementById('loadingBar');
(async() => {
const promises = new Array(100)
.fill(0)
.map(task);
loadingBar.textContent = `Loading...`;
await Promise.all(promises);
loadingBar.textContent = `Loading Finished`;
})();
Now imagine if this takes 30 seconds to complete. All the user will see on screen is the text 'Loading...' whilst it is in progress.
That is not a very useful message!
Let's improve this now by updating the progress text after each task has resolved.
The code snippet below is the full implementation.
const loadingBar = document.getElementById('loadingBar');
async function task() {
return new Promise(res => {
setTimeout(res, Math.random() * 5000);
})
}
function loadingBarStatus(current, max) {
loadingBar.textContent = `Loading ${current} of ${max}`;
}
(async() => {
let current = 1;
const promises = new Array(100)
.fill(0)
.map(() => task().then(() => loadingBarStatus(current++, 100)));
await Promise.all(promises);
loadingBar.textContent = `Loading Finished`;
})();
Now, you can see that as each promise is resolved, the counter is incremented and displayed to the user.
Conclusion
In short, all you need to do is update the progress as each promise is resolved.
I hope you found this short tutorial helpful.
Let me know your thoughts in the comments below. 😊
Top comments (7)
This approach works fine if you don't care about any of the results of the promises, but not if you need to use those results (for example, if they're API responses):
Instead, you can store the promises to an intermediate variable, then loop over it with
forEach
for the side-effectey stuff, meanwhileawait
ing it withPromise.all
so you can do something with the result:You could further abstract the loading bar logic something like this:
Nice! I came up with something Promise-agnostic, although a little more cumbersome to use because of that:
Used something like this:
Thank you OP for the article and video, of course. 🙏
Too complex.
I can't see an actual use case where this would be that useful. If you're waiting thirty seconds for something to complete and block the user, you're doing something wrong. Either way, this should be implemented on the backend with a progress endpoint or sth.
Hey, thanks for your comment.
A use case I used the other week was deleting multiple rows/data from a grid.
Another common example is app initialisation, how often do you open an app and there is a loading widget for a few seconds before you can use it?
I see what you mean, yet consider this.
When you'd delete data from the grid, I'm guessing that happens on the backend, right? Then, a better approach would be to remove the deleted items from the grid locally and notify the user whether the operation was successful or not.
For the second example, consider lazy loading some modules, or using placeholders for components being loaded (like skeletons).
I wouldn't say a better approach, but definitely a viable one dependent on the requirements 👍 Thanks for adding your comment though, definitely an alternative approach to consider