If you have a load of Promises or Async/Await functions and you want to wait until they are all done, then this blog article can show you how.
If you are a JavaScript programmer and you're as old as me, then you might remember the days of callback hell... so called because there was no other way to wait for a load of requests to be completed before you could do something else. And the only way to do this was to nest callbacks within functions that called other functions and so on an so fourth, and it was a f🤬🤬king mess.
Then came Promises, which were a much better way to handle asynchronous code, and then not log after that came Async Await... and if like me, you'll love how much cleaner it can make your code.
Anyhow, I thought I'd put this page up here as a simple demonstration of how we can use Promises and Async Await functions within an Array and then use Promise.all() to wait for them all to complete.
To see this in action take a look at this Async Await and Promise All Demo
So here's the React code, and I'll explain it below.
/* This file is a demonstration of how we can chain a load of Async Await or Promise functions together, and use an array with a Promise.all() to execute them all and then do some thing once they are finished! */ import { useState } from "react"; export default function AsyncAwaitPromiseAll() { const nameFixer = (name: string) => { const nameLower = name.trim().toLowerCase(); const firstLetter = nameLower.charAt(0); return { nameLower, firstLetter } } const [ fetches, setFetches ] = useState(0); const [ timeouts, setTimeouts ] = useState(0); const [ imageLoads, setImageLoads ] = useState(0); const [ timeTaken, setTimeTaken ] = useState<null | string>(null); // Async Fetching demo const asyncAwaitFunctionDemo = async ( name : string ) => { const { nameLower, firstLetter } = nameFixer( name ) const resp = await fetch(`https://search.3dnames.co/2021-12-08/${firstLetter}/__${nameLower}.json`, {cache: "no-store"}); const data = await resp.json(); setFetches(prev => prev + 1); console.log('JSON Fetched:', name, Object.keys(data).length) return data; } // Image loader const randomImageLoader = ( name : string, style : string ) => { const { nameLower, firstLetter } = nameFixer( name ); return new Promise((resolve, reject) => { let img = new Image() img.onload = () => { setImageLoads(prev => prev + 1); console.log('Image Loaded:', img.src) resolve(img.height) } img.onerror = reject img.src = `https://cdn.3dnames.co/previews/${style}/500x500/${firstLetter}/${style}${nameLower}3dxx.jpg` }) } // Random timeout Promise - can be called with await const randomTimeOutFunction = () => { const time = Math.round(Math.random() * 2000) return new Promise((resolve) => { setTimeout(() => { console.log('Timeout: ',time) setTimeouts(prev => prev + 1); resolve('resolved'); },time); }); } // Run our process... const startProcess = async () => { setFetches(0); setTimeouts(0); setImageLoads(0); setTimeTaken(''); const start = Date.now(); // Lets create some random timeouts... const timeOutPromises = []; for (var i = 0; i < 30; i++) { timeOutPromises.push(randomTimeOutFunction()); } const allPromises = [ ...timeOutPromises, asyncAwaitFunctionDemo('Joe'), asyncAwaitFunctionDemo('Luna'), asyncAwaitFunctionDemo('Giselle'), asyncAwaitFunctionDemo('Digby'), asyncAwaitFunctionDemo('Daniel'), asyncAwaitFunctionDemo('Kamil'), asyncAwaitFunctionDemo('Phill'), asyncAwaitFunctionDemo('Alex'), asyncAwaitFunctionDemo('Gavin'), randomImageLoader("Joe","cd45"), randomImageLoader("Steve","cd46"), randomImageLoader("Ralph","cd47"), randomImageLoader("Gavin","cd10"), randomImageLoader("Alex","cd11"), randomImageLoader("Phill","ps31"), ] await Promise.all(allPromises); // Hint: use Promise.allSettled() if you don't care if some fail or you want to log the failures separately const end = Date.now(); setTimeTaken(`${end - start}ms`); } return ( <div className="px-4 sm:px-6 lg:px-8"> <div className="sm:flex sm:items-center"> <div className="sm:flex-auto"> <h1 className="text-xl font-semibold text-gray-900">Async Await and Promise all</h1> <p className="mt-2 text-sm text-gray-700"> This shows how a Promise.all can be used on a load of async await functions. </p> <ul className="list-disc ml-5 mt-5"> <li>⏳ Number of random timeouts: <b>{timeouts}</b></li> <li>🔄 Number of HTTP fetch requests: <b>{fetches}</b></li> <li>🖼️ Number of image loads: <b>{imageLoads}</b></li> </ul> <p className="text-xl mt-5"> ⏰ Total Time taken: { timeTaken === '' ? <> <span className="animate-spin inline-block ml-2">⌛️</span> </> : <b>{timeTaken}</b>} </p> </div> <div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none text-center w-52"> <button type="button" onClick={startProcess} className="mx-auto inline-flex items-center justify-center rounded-md border border-transparent bg-cyan-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2 sm:w-auto" > Run Simulation </button> <small className="block text-gray-400 mt-2">Hint: Disable cache first by opening network tab in console</small> </div> </div> </div> ) }
The 3 functions named asyncAwaitFunctionDemo
, randomImageLoader
, & randomTimeOutFunction
are either Promises or Async/Await functions, we are then creating an array of these functions with const allPromises = [
- now we could simply run these as is and they will all execute, however unless we add the line await Promise.all(allPromises);
- we will never know when they have all finished.
Note: We have used the Promise.all()
here, and if any of our chained functions failed, then the whole process would fail. If you want to run all the functions and not care if they fail or if you want to be able to log them, then you can use Promise.allSettled()
instead. See this article: Changing Async/Await to Promises.allSettled() to Speed Up API Calls in Node.JS - for a good example of how to use status: "fulfilled" or status: "rejected" to log the results.