Ready to discover a secret weapon in JavaScript? ๐ Today, weโre diving into Generators, those incredible functions that can pause and resume their execution on demand. Cool, right? Letโs unpack this magic with real-world examples to level up your coding game!
What are Generators?
In simple terms, a Generator is a special function in JavaScript. Unlike regular functions that run from start to finish, generators can yield values at different points and pick up right where they left off.
Generators use the function* syntax **(note the asterisk) and the **yield keyword to pause execution. When you invoke a generator function, it doesnโt immediately execute like a regular function. Instead, it returns a special object called an iterator, which is capable of pausing and resuming execution.
Letโs start with a simple example to see how generators work:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
return "Done!";
}
const gen = myGenerator();
console.log(gen);
// Output : Object [Generator] {}
When you call a generator function, it doesnโt execute its code immediately. Instead, it returns a special Generator object- an iterator that follows the Iterator Protocol.
console.log(gen.next()); // Output : { value: 1, done: false }
console.log(gen.next()); // Output : { value: 2, done: false }
console.log(gen.next()); // Output : { value: 3, done: false }
console.log(gen.next()); // Output : { value: "Done!", done: true }
Each call to its next() **method runs the generator until the next yield, returning a { value, done } pair. When the generator has no more values to yield, **it returns { done: true }.
When to Use Generators?
โข Lazy Evaluation: Fetch or process data only when needed.
โข State Machines: Handle complex state transitions.
โข Iterative Tasks: Paginate, process streams, or generate sequences dynamically.
Async Generators:
We'll create an async generator that simulates fetching paginated data from an API. For simplicity, weโll return hardcoded "pages" of data.
async function* fetchUserPages() {
let page = 1;
while (page <= 3) {
console.log(`Fetching page ${page}...`);
// Simulating an API request that returns users for the current page
const users = await new Promise(resolve => {
setTimeout(() => {
resolve([`User ${page * 1}`, `User ${page * 2}`, `User ${page * 3}`]);
}, 1000); // Simulating 1 second delay for each page
});
yield users; // Yield the users from the current page
page++;
}
}
The await new Promise() part simulates an asynchronous API request that returns an array of users after a delay (1 second).
Each time we yield users, the generator pauses and sends the data.
(async () => {
const userGenerator = fetchUserPages();
for await (const users of userGenerator) {
console.log("Received Users:", users);
}
console.log("All pages fetched!");
})();
Output (in the console):
Fetching page 1...
Received Users: [ 'User 1', 'User 2', 'User 3' ]
Fetching page 2...
Received Users: [ 'User 2', 'User 4', 'User 6' ]
Fetching page 3...
Received Users: [ 'User 3', 'User 6', 'User 9' ]
All pages fetched!
Key Takeaways:
Async generators with the for await...of loop offer a clean and efficient way to handle asynchronous operations. The await inside the generator ensures non-blocking execution, so other tasks can continue while waiting for data. The yield keyword allows us to return data in chunks (pages), keeping memory usage low. The for await...of loop simplifies consuming the generator, automatically managing asynchronous behavior, making the code more readable and intuitive.
Final Thoughts ๐
Async generators may seem tricky at first, but with some practice, theyโll quickly become a powerful tool in your JavaScript skillset. Start by experimenting with the basics, and soon you'll be handling asynchronous workflows like a pro! ๐ช
_Want to share how youโve used async generators? Drop a comment belowโletโs learn together! ๐
Happy coding! ๐ป_
Top comments (0)