Today I learned several important JavaScript concepts related to asynchronous programming and iteration. Instead of only learning how these features work, I focused on understanding why they were introduced, the problems they solve, and where they are used in real-world applications.
Async/Await – Syntax Sugar over Promises
One of the biggest things I learned today is that async/await does not replace Promises. It is simply a cleaner and more readable way to work with them.
When we write:
async function getUser() {
const user = await fetchUser();
return user;
}
JavaScript is still using Promises internally. The await keyword pauses the execution of the async function until the Promise settles, making asynchronous code look very similar to synchronous code.
Without async/await, the same logic would require multiple .then() calls, making complex asynchronous code harder to read and maintain.
Handling Async Errors with try...catch
When using Promises directly, errors are usually handled with .catch().
fetchUser()
.then(user => console.log(user))
.catch(err => console.error(err));
With async/await, the same error handling becomes much cleaner using try...catch.
async function loadUser() {
try {
const user = await fetchUser();
console.log(user);
} catch (err) {
console.error(err);
}
}
I learned that try...catch only works with await inside an async function. If the awaited Promise rejects, JavaScript throws that rejection as an exception, which is then caught by the catch block.
This makes asynchronous error handling look just like normal synchronous error handling.
Promise.all()
Promise.all() is used when multiple asynchronous tasks need to finish before continuing.
For example, imagine a dashboard that needs to load:
- User Profile
- Orders
- Shopping Cart
- Recommendations
All these requests can start at the same time, but the dashboard should only be shown after every request is completed.
await Promise.all([
fetchProfile(),
fetchOrders(),
fetchCart(),
fetchRecommendations()
]);
I also learned that Promise.all() does not start the promises. The promises begin executing when they are created (or when the function calls are evaluated). Promise.all() simply waits for all of them to settle successfully.
Can We Display Data One by One?
Yes!
Instead of waiting for every request, each section of a webpage can be displayed as soon as its data arrives.
For example:
- Show the profile first.
- Then show the orders.
- Then display the cart.
- Finally load the recommendations.
Many websites like YouTube and Amazon use this approach to improve the user experience.
Promise.allSettled()
Sometimes we don't want the whole operation to fail just because one task fails.
For example, while uploading five files:
- File 1 ✔️
- File 2 ❌
- File 3 ✔️
- File 4 ✔️
- File 5 ❌
Using Promise.all(), the first failure rejects the entire operation.
Using Promise.allSettled(), JavaScript waits for every promise to finish and tells us which ones succeeded and which ones failed.
This is useful for:
- File uploads
- Data synchronization
- Background jobs
- Health checks
Promise.race()
Promise.race() returns the result of the first promise that settles.
A common example is implementing request timeouts.
Promise.race([
fetch("/user"),
timeoutPromise
]);
If the server responds first, we use the response.
If the timeout finishes first, we stop waiting.
Promise.any()
Promise.any() returns the first successful promise.
Rejected promises are ignored unless every promise fails.
This is useful when working with:
- Multiple mirror servers
- Backup APIs
- CDN replicas
The application simply uses the first successful response.
Promise Combinators Summary
| Method | Purpose |
|---|---|
| Promise.all() | Wait for every promise to fulfill |
| Promise.allSettled() | Wait for every promise to settle, whether fulfilled or rejected |
| Promise.race() | Use the first promise that settles |
| Promise.any() | Use the first successful promise |
Iterators
I learned that JavaScript needed a common way to loop through different types of collections like arrays, strings, maps, and sets.
An iterator is simply an object with a next() method.
Each call to next() returns:
{
value,
done
}
for...of internally uses iterators to retrieve values one by one.
Generators
Writing iterators manually requires managing state and keeping track of the current position.
Generators make this much easier.
A generator is declared using:
function* numbers() {
yield 1;
yield 2;
yield 3;
}
The yield keyword pauses execution and remembers the current state.
Each call to next() resumes execution from where it previously stopped.
Generators automatically create iterators, making iteration much simpler.
Async Iterators
Normal iterators work only with values that are immediately available.
But what if the next value comes from:
- A server
- A database
- A file
- A WebSocket
In these situations, JavaScript introduced async iterators.
Unlike normal iterators, next() returns a Promise that eventually resolves to:
{
value,
done
}
This allows JavaScript to wait until the next value becomes available.
Async Generators
Just as generators simplify iterators, async generators simplify async iterators.
They are declared like this:
async function* stream() {
yield "First";
yield "Second";
yield "Third";
}
They can be consumed using:
for await (const item of stream()) {
console.log(item);
}
This makes working with asynchronous streams much easier.
Where Async Iterators Are Used
Some real-world examples include:
- Streaming large files
- Reading database records in batches
- Live chat messages
- WebSocket communication
- AI response streaming (like ChatGPT)
- Video or audio streaming
Instead of loading everything into memory, data is processed one piece at a time.
My Biggest Takeaways
-
async/awaitis syntax sugar over Promises and makes asynchronous code easier to read. -
try...catchprovides clean error handling for asynchronous code written withasync/await. -
Promise.all()waits for every promise and fails if any one fails. -
Promise.allSettled()gives the result of every promise, whether it succeeds or fails. -
Promise.race()returns whichever promise settles first. -
Promise.any()returns the first successful promise. - Promises begin running when they are created;
Promise.all()only waits for them. - A webpage doesn't always need to wait for every API before showing content—independent sections can be rendered progressively.
- Iterators provide a standard way to access values one by one.
- Generators automatically create iterators and use
yieldto pause and resume execution. - Async iterators return promises from
next(), allowing values to arrive over time. - Async generators combine
asyncandyield, making asynchronous data streams much easier to work with.
Top comments (0)