# Series: JavaScript Iterators & Generators (4 Parts) — Luca Del Puppo > Complete 4-part technical series "JavaScript Iterators & Generators" by Luca Del Puppo (https://delpuppo.net/blog/series/js-iterators-generators/). Ordered chronologically from Part 1 to Part 4. - **Canonical Series URL**: https://delpuppo.net/blog/series/js-iterators-generators/ - **Hashnode Series Mirror**: https://blog.delpuppo.net/series/js-iterators-generators - **Blog Index LLMs.txt**: https://delpuppo.net/blog/llms.txt ## Series Curriculum 1. [Part 1: Iterate Like a Pro: Mastering JavaScript Iterators for Effortless Code](https://delpuppo.net/blog/js-iterators-generators/iterate-like-a-pro-mastering-javascript-iterators-for-effortless-code/) (Aug 25, 2023) — Welcome to this blog post, where we're about to unravel the power and versatility of JavaScript... ([Article LLMs.txt](https://delpuppo.net/blog/js-iterators-generators/iterate-like-a-pro-mastering-javascript-iterators-for-effortless-code/llms.txt)) 2. [Part 2: Unleashing the Power: Demystifying JavaScript Generators](https://delpuppo.net/blog/js-iterators-generators/unleashing-the-power-demystifying-javascript-generators/) (Sep 1, 2023) — Welcome to this blog post, where we're diving into the fascinating world of JavaScript generators. If... ([Article LLMs.txt](https://delpuppo.net/blog/js-iterators-generators/unleashing-the-power-demystifying-javascript-generators/llms.txt)) 3. [Part 3: Mastering JavaScript Async Iterators: Unlocking Asynchronous Magic](https://delpuppo.net/blog/js-iterators-generators/mastering-javascript-async-iterators-unlocking-asynchronous-magic/) (Sep 8, 2023) — In the ever-evolving landscape of JavaScript, staying up-to-date with its latest features is crucial... ([Article LLMs.txt](https://delpuppo.net/blog/js-iterators-generators/mastering-javascript-async-iterators-unlocking-asynchronous-magic/llms.txt)) 4. [Part 4: JavaScript Async Generators Unleashed: Harnessing Asynchronous Power](https://delpuppo.net/blog/js-iterators-generators/javascript-async-generators-unleashed-harnessing-asynchronous-power/) (Sep 15, 2023) — In the dynamic realm of JavaScript, the ability to efficiently handle asynchronous operations is... ([Article LLMs.txt](https://delpuppo.net/blog/js-iterators-generators/javascript-async-generators-unleashed-harnessing-asynchronous-power/llms.txt)) --- ## Full Series Content ### Part 1 of 4: Iterate Like a Pro: Mastering JavaScript Iterators for Effortless Code - **URL**: https://delpuppo.net/blog/js-iterators-generators/iterate-like-a-pro-mastering-javascript-iterators-for-effortless-code/ - **Published**: Aug 25, 2023 - **Tags**: javascript, iterators, iterator, js Welcome to this blog post, where we're about to unravel the power and versatility of JavaScript iterators. If you've ever found yourself working with data collections and wondered if there's a more efficient and elegant way to navigate them, you're in the right place. In the realm of modern web development, efficiently managing and manipulating data is a crucial skill. This is where JavaScript iterators come into play, offering a systematic approach to traversing various data structures such as arrays, maps, and sets. Whether you're a beginner eager to understand the basics or an experienced developer looking to fine-tune your code, grasping the concept of iterators can significantly enhance your programming journey. In this blog post, we'll embark on a comprehensive exploration of JavaScript iterators. We'll break down their fundamental principles, explore practical examples of their usage, and demonstrate how they can simplify complex data operations. By the time you finish reading, you'll not only understand iterators inside and out but also be equipped to leverage their potential to write more concise, readable, and efficient code. So, whether you're striving to make your codebase more elegant or seeking to optimize your data manipulation techniques, join us as we delve into the world of JavaScript iterators. From novice to expert, there's something here for everyone. Let's dive in! Built-in in Javascript exists many structures that implement the Iterators pattern, for instance: Array, Set and Map. In Javascript, an object to be iterable must implement the Iterable interface. But what is the Iterable interface? First, to be iterable, an object must have a `next` method. This method must return two properties: `done` and `value`. Done is used to detect if the iteration is completed; instead, the value contains the current value. Last but not least, if you want your object to become an iterator, you must expose the iterable interface in the `Symbol.iterator` of your object, like in this example. ```ts const array = [1, 2, 3, 4, 5]; const iterator = array[Symbol.iterator](); for (let result = iterator.next(); !result.done; result = iterator.next()) { console.log(result.value); } ``` For example, here is a range function implemented as an iterator. ```ts const range = (start: number, end: number): Iterable => { return { [Symbol.iterator]() { let n = start; return { next() { console.log("range next"); if (n > end) { return { done: true, value: null }; } return { done: false, value: n++ }; }, }; }, }; }; ``` As you can notice, this function accepts two numbers, start and end and returns a new object with a single property, in this case, the iterator property. Then, inside this function, there is the next function that, on every call, checks if the current value is greater than the end and, if true, returns a new object with done as true and value as null; else returns an object with done false and value with the current value. The beautiful thing about iterator is that javascript only does something once you ask for the next value. Every iterator can be iterated using the for-of loop ```ts for (const num of range(1, 10)) { console.log(num); } ``` or using its native method, so calling the `Symbol.iterator` function and then using the next method and checking the done property if it is true or not. ```ts const rangeIterator = range(1, 10)[Symbol.iterator](); for (let result = rangeIterator.next(); !result.done; result = rangeIterator.next()) { console.log(result.value); } ``` It's possible to copy all the iterator values in an Array using the spread operator too. ```ts for (const num of [...range(1, 10)]) { console.log(num); } ``` Iterator also has another method, the `return` method. This method is used in case the code doesn't complete the iteration. Imagine the loop call a break or a return; in this case, JavaScript under the hood calls the `return` method for us. In this method, we can handle whatever we need. We may need to reset something or check the current value of the iterator. ```ts const range = (start: number, end: number): Iterable => { return { [Symbol.iterator]() { let n = start; return { next() { console.log("range next"); if (n > end) { return { done: true, value: null }; } return { done: false, value: n++ }; }, return() { console.log("range return"); return { done: true, value: null }; }, }; }, }; }; for (const num of range(1, 10)) { if (num > 5) break; console.log(num); } ``` Iterators are powerful, and we can also create functions that accept an iterator and manipulate it to return another iterator. For instance, we can create a map function that accepts an iterator and returns another with a callback specified by the user. ```ts function mapIterable( iterable: Iterable, callback: (value: T) => U ): Iterable { return { [Symbol.iterator]() { const iterator = iterable[Symbol.iterator](); return { next() { console.log("mapIterable next"); const { done, value } = iterator.next(); if (done) { return { done: true, value: null }; } return { done, value: callback(value) }; }, return() { console.log("mapIterable return"); if (iterator.return) { iterator.return(); } return { done: true, value: null }; }, }; }, }; } ``` All the info said before is true also for this new iterator. JavaScript does nothing until the codebase doesn't ask for the next value; the same is true for the return method, and now you can compose the range iterators with the map iterator to build a new one. ```ts const mapRange = mapIterable(range(1, 10), value => value * 10); for (const num of mapRange) { if (num > 50) break; console.log(num); } ``` Ok folks, that's all! If you want to dive deep with Iterators, don't waste my video on my [Youtube Channel](https://www.youtube.com/@Puppo_92/). > 🎬 **Watch / View Embedded Resource:** [https://www.youtube.com/watch?v=BSctSkMGtiM](https://www.youtube.com/watch?v=BSctSkMGtiM) In conclusion, understanding and utilizing JavaScript iterators can greatly enhance your ability to work with collections of data in a more elegant and efficient manner. With iterators, you can streamline your code, improve its readability, and reduce memory consumption by processing data one element at a time. This powerful concept empowers developers to implement custom iteration behaviour, making their code more adaptable to different scenarios. By grasping the fundamentals of iterators, such as the `next()` method and the concept of iterable objects, you open the door to more sophisticated programming techniques and design patterns. Whether you're working with arrays, maps, sets, or other data structures, iterators provide a standardized way to traverse through data and perform operations without unnecessary complexity. As the JavaScript language continues to evolve, iterators remain a foundational concept that plays a crucial role in modern programming paradigms. Incorporating iterators into your coding arsenal equips you with a versatile tool for managing data, ultimately leading to cleaner, more maintainable, and more robust code. So, as you embark on your journey to master JavaScript iterators, remember that they are more than just technical features; they represent a shift in how you approach and solve problems in your code. With practice and exploration, you'll be able to wield iterators effectively, making your codebase more efficient and your development experience more enjoyable. Thanks for reading and Happy coding! 😃 👩💻 👨💻 N.B. you can find the code of this article [in the GitHub repository](https://github.com/Puppo/javascript-iterators-and-generators/tree/01-iterators)! > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 2 of 4: Unleashing the Power: Demystifying JavaScript Generators - **URL**: https://delpuppo.net/blog/js-iterators-generators/unleashing-the-power-demystifying-javascript-generators/ - **Published**: Sep 1, 2023 - **Tags**: javascript, generators, generator, js Welcome to this blog post, where we're diving into the fascinating world of JavaScript generators. If you're looking to take your coding skills to the next level and explore a powerful tool for managing operations, you've come to the right place. In the fast-paced landscape of web development, managing tasks and handling complex flows of data can be quite challenging. This is where JavaScript generators come into play. These remarkable constructs introduce a new level of control and flexibility, allowing developers to write code that can be paused, resumed, and iterated step by step. In this blog post, we'll take you on a journey through the core concepts of JavaScript generators. We'll unravel their syntax, explore their capabilities, and showcase examples of how they can simplify your code and make it more manageable. By the end of this read, you'll not only understand the ins and outs of generators but also be equipped with the knowledge to start using them effectively in your own projects. Buckle up as we embark on this exploration of JavaScript generators, unravelling their potential and discovering how they can revolutionize the way you write code. Whether you're a seasoned developer or just starting with JavaScript, there's something valuable here for you. Let's begin! A generator in JavaScript is a function that, after the function keyword, has a `*`. It's not possible to create generators in JavaScript using arrow functions. Generators are special functions that implement the Iterable interface very simply; you can learn more about them in this blog post. The particularity of generators is the `yield` keyword. Using the `yield`, we can return a new value to the iteration but without ending the function execution, simply putting it on pause, until the next request for the next value and until it's not completed. ```ts function* range(start: number, end: number): Iterable { let n = start; while (n <= end) { console.log("range next"); yield n++; } } ``` As you can notice, the range generator is a simple generator function, and it's understandable from the `*` near-function keyword. Using a simple while, the function checks if the current value is less or equal to the end value and if true, using the `yield` returns the value; else exists from the while and closes the generator. Like iterators, generators only run once someone calls for the `next` value. It's possible to go through a generator using a simple for-of-loop. ```ts for (const num of range(1, 10)) { console.log(num); } ``` Using the Iterable interface ```ts const iterator = range(1, 10)[Symbol.iterator](); while (true) { const { done, value } = iterator.next(); if (done) break; console.log(value); } ``` Or using the spread operator to save all the values in an Array. ```ts for (const num of [...range(1, 10)]) { console.log(num); } ``` It's important to remember that if you use the spread operator, you will lose all the generators' benefits because it runs the generator until it's not completed to copy all the values in the array. Like iterators also, generators can be combined. For instance, we can create a map generator function that accepts an iterator and a transformation function and returns the computation inside the transformation with the value emitted by the generator. And that's the result. ```ts function* map( iterable: Iterable, callback: (value: T) => U ): Iterable { for (const value of iterable) { yield callback(value); } } const mapRange = map(range(1, 10), value => value * 10); for (const num of mapRange) { console.log(num); } ``` That's all folks. You can find a video about them on my [Youtube channel](https://www.youtube.com/@Puppo_92/). > 🎬 **Watch / View Embedded Resource:** [https://youtu.be/mi2JlrXBRIs](https://youtu.be/mi2JlrXBRIs) In conclusion, JavaScript generators stand as a remarkable tool in the arsenal of every developer striving for more efficient and flexible programming. Through their unique ability to pause and resume execution, generators provide an innovative solution to managing complex flows of data and operations. Throughout this blog post, we've journeyed through the syntax, use cases, and benefits of JavaScript generators. We've seen how they can simplify code, making it more readable and maintainable. By harnessing the power of generators, you can transform the way you approach programming challenges. These constructs not only offer a structured approach to handling tasks but also contribute to writing code that is less error-prone and easier to reason about. As you venture into the realm of generators, remember that practice is key. Experiment with different scenarios, explore their interplay with other techniques and gradually integrate them into your projects. With time, you'll become proficient in leveraging generators to create responsive and efficient applications. So, whether you're a front-end developer aiming to tame complex UI interactions or a back-end engineer dealing with intricate data processing, generators offer a fresh perspective on tackling programming. Embrace their power, refine your skills, and elevate your coding prowess in the dynamic world of JavaScript. Thank you for reading and happy generating! 👩💻 👨💻 😜 N.B. you can find the code of this post [in the GitHub repository](https://github.com/Puppo/javascript-iterators-and-generators/tree/02-generators). > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 3 of 4: Mastering JavaScript Async Iterators: Unlocking Asynchronous Magic - **URL**: https://delpuppo.net/blog/js-iterators-generators/mastering-javascript-async-iterators-unlocking-asynchronous-magic/ - **Published**: Sep 8, 2023 - **Tags**: javascript, iterators, async, asynchronous, iterator, js In the ever-evolving landscape of JavaScript, staying up-to-date with its latest features is crucial for writing efficient and modern code. One such feature that has garnered significant attention is the Async Iterator. While iterators have long been an integral part of JavaScript for sequential data processing, the introduction of asynchronous programming patterns brought about the need for asynchronous iteration. Imagine effortlessly traversing through data streams that might involve fetching data from APIs, reading from files, or any other asynchronous data source. This is precisely where Async Iterators shine, providing a seamless and elegant solution to handle such scenarios. In this blog post, we'll delve into the world of JavaScript Async Iterators, exploring their fundamentals, understanding their benefits, and uncovering how they can be a game-changer in writing robust asynchronous code. Whether you're a seasoned developer looking to expand your skill set or a newcomer curious about advanced JavaScript techniques, this blog post is for you. We will unravel the power of Async Iterators and take your asynchronous programming skills to new heights. Before we jump into the code, let's understand what async iterators are. In JavaScript, iterators are objects that allow us to loop over collections. Async iterators take this concept a step further by allowing us to handle asynchronous operations, like fetching data from APIs or reading from streams. Creating an async iterable is simple. We use the `Symbol.asyncIterator` to define the async iterator method inside an object. This method will return an object with the `next` method that resolves a promise containing the `next` value in the asynchronous sequence. Let's take a look at an example. ```ts const getUsers = (ids: number[]): AsyncIterable => { return { [Symbol.asyncIterator]() { let i = 0; return { async next() { console.log("getUsers next"); if (i === ids.length) { return { done: true, value: null }; } const data = await fetch( `https://reqres.in/api/users/${ids[i++]}` ).then(res => res.json()); return { done: false, value: data }; }, }; }, }; }; ``` Imagine you have a list of IDs and want to read the user data only if needed. Using AsyncIterators, you can create a function that handles the API and returns the result of every request on every iteration, making the code more transparent. To consume the values of an async iterable, we use the `for-await-of` loop. This loop works just like the regular `for-of` loop, but it's designed specifically for asynchronous iterables. ```ts for await (const user of getUsers([1, 2, 3, 4, 5])) { console.log(user); } ``` Error handling is crucial when dealing with asynchronous operations. Async iterators allow us to handle errors using `try-catch` blocks around the `for-await-of` loop. ```ts try { for await (const user of getUsers([1, 2, 3, 4, 5])) { console.log(user); } } catch (err) { console.error(err); } ``` AsyncIterator runs code only if needed, so until you don't call the next method, nothing happens, like for Iterators. The `return` method exists also for AsyncIterators. This method is used in case the code doesn't complete all the iterations. Imagine the loop calls a break or a return; in this case, JavaScript under the hood calls the `return` method for us. In this method, we can handle whatever we need. We may need to reset something or check the current value of the iterator. ```ts const getUsers = (ids: number[]): AsyncIterable => { return { [Symbol.asyncIterator]() { let i = 0; return { ... async return() { console.log("getUsers return"); return { done: true, value: null }; }, }; }, }; }; ``` Async Iterators are powerful like Iterators, and we can create functions that accept an AsyncIterator and manipulate it to return another Async Iterator. For instance, we can create a map function that accepts an Async Iterator and returns another with a callback specified by the user. ```ts function map(iter: AsyncIterable, fn: (v: T) => U): AsyncIterable { return { [Symbol.asyncIterator]() { const iterator = iter[Symbol.asyncIterator](); return { async next() { console.log("map next"); const { done, value } = await iterator.next(); if (done) return { done, value: null }; return { done, value: fn(value) }; }, async return() { console.log("map return"); if (iterator?.return) await iterator?.return(); return { done: true, value: null }; }, }; }, }; } ``` These functions have all the benefits said before. Javascript does nothing until the codebase doesn't ask for the next function; the same is true for the `return` method, and now you can compose the `getUsers` with the `map` to build a new Async Iterator. ```ts const iterator = map(getUsers([1, 2, 3, 4, 5]), user => user.data.id) for await (const num of iterator) { if (num === 3) break; console.log(num); } ``` And there you have it a deep dive into the world of asynchronous iterators in JavaScript. They provide an elegant solution to working with asynchronous data streams, making your code more organized and efficient. Experiment with async iterators in your projects, and you'll be amazed at how they simplify complex asynchronous workflows. I also created a video on my [Youtube channel](https://www.youtube.com/@Puppo_92/), that you can find below. > 🎬 **Watch / View Embedded Resource:** [https://youtube.com/watch?v=0_mzZ9QOebg](https://youtube.com/watch?v=0_mzZ9QOebg) If you found this content helpful, like and share it. And if you have any questions, feedback, or doubts, let me know in the comments 😀 Thanks for reading! And Happy coding! 👩💻 👨💻 N.B. you can find the code of this post [in the GitHub repository](https://github.com/Puppo/javascript-iterators-and-generators/tree/03-async-iterators). > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) --- ### Part 4 of 4: JavaScript Async Generators Unleashed: Harnessing Asynchronous Power - **URL**: https://delpuppo.net/blog/js-iterators-generators/javascript-async-generators-unleashed-harnessing-asynchronous-power/ - **Published**: Sep 15, 2023 - **Tags**: javascript, generator, generators, async, js, asynchronous In the dynamic realm of JavaScript, the ability to efficiently handle asynchronous operations is paramount for building responsive and robust applications. While Async Iterators have already proven their worth in managing asynchronous data streams, JavaScript takes another leap forward with Async Generators. Imagine a world where you can effortlessly produce a continuous stream of asynchronous data while dynamically controlling its flow. This is precisely where Async Generators excel, revolutionizing the way we handle and manipulate asynchronous sequences. In this blog post, we're diving deep into the captivating world of JavaScript Async Generators. We'll unveil how Async Generators empower developers to create elegant solutions for complex asynchronous scenarios. Whether you're grappling with real-time data synchronization, asynchronous data processing, or any scenario demanding fine-tuned control over data flow, join us on this journey as we unravel the capabilities, nuances, and transformative power of JavaScript Async Generators. Before we delve into Async Generators, let's recap the basics of asynchronous programming. In JavaScript, asynchronous tasks are crucial for handling time-consuming operations without blocking the main thread. Functions like setTimeout, AJAX requests, and Promises have become commonplace in managing asynchronous operations. Generators, denoted by `function*` syntax, are a powerful feature introduced in ES6. They allow us to pause and resume the execution of a function, providing a valuable mechanism for lazy evaluation. The `yield` keyword within a generator function lets us yield values individually, pausing the function's execution until the next value is requested. If you want to learn more, there is relative content about them in this series. Now, imagine combining the magic of asynchronous operations with the versatility of generators. Enter Async Generators! These supercharged functions enable us to work with asynchronous data streams more intuitively and elegantly. An async generator function is declared using `async function*` syntax. Inside, the `yield` keyword can be combined with `await` to handle asynchronous operations gracefully. This means we can yield promises, fetch data and seamlessly integrate it into our data stream. The real beauty of async generators lies in their ability to simplify complex asynchronous tasks. They're particularly handy when dealing with data streams from sources like databases, APIs, or even real-time event streams. Imagine processing a continuous stream of sensor data or handling multiple concurrent requests without drowning in callback hell! Let's take a look at an example ```ts async function* getUsers(ids: number[]) { for (const id of ids) { console.log("getUsers next"); const data = fetch(`https://reqres.in/api/users/${id}`).then(res => res.json() ); yield data; } } ``` Using a `for-of` loop, the function iterates over the IDs and fetches the data for every user on every iteration. However, using the yield, stop the execution and resume it only when the caller asks for the next value. Like Async Iterators, also Async Generators can be iterated using a `for-await-of` loop. ```ts for await (const user of getUsers([1, 2, 3, 4, 5])) { console.log(user); } ``` Error handling and completion are crucial aspects of asynchronous programming. With async generators, we can catch errors using try-catch blocks within the generator function. Plus, we can signal the end of data using the 'return' statement, closing the loop on our data stream. ```ts try { for await (const user of getUsers([1, 2, 3, 4, 5])) { console.log(user); } } catch (err) { console.error(err); } async function* getUsers(ids: number[]) { for (const id of ids) { console.log("getUsers next"); const data = fetch(`https://reqres.in/api/users/${id}`).then(res => res.json() ); if (id > 4) return data; // <-- Return yield data; } } ``` Like Generators, also Async Generators can be combined. For instance, we can create an Async Generator `map` function that accepts an Async Generator and a transformation function and returns a new async generator with the transformations. ```ts async function* map( asyncIterable: AsyncIterable, callback: (value: T) => U ) { for await (const item of asyncIterable) { yield callback(item); } } ``` Asynchronous programming in JavaScript continues to evolve, and Async Generators are a testament to its dynamic nature. With their power to elegantly manage complex asynchronous tasks, async generators offer a glimpse into the future of streamlined coding. So, fellow developers, embrace this innovative tool and unlock new possibilities in your projects. I also created a video about Async Generators on my [Youtube channel](https://www.youtube.com/@Puppo_92/) and you can find it below. > 🎬 **Watch / View Embedded Resource:** [https://youtu.be/0uQvNZ8th08](https://youtu.be/0uQvNZ8th08) That's a wrap for today's discussion on JavaScript Async Generators. I hope you found this exploration insightful and inspiring. Remember, staying curious and open to new concepts is the key to mastering your craft in the ever-evolving programming landscape. If you enjoyed this post, like and share it. And if you have questions, feedback or doubts let me know in the comments. Until next time, happy coding and thanks for reading 👩💻👨💻 N.B. you can find the code of this post [in the GitHub repository](https://github.com/Puppo/javascript-iterators-and-generators/tree/04-async-generators). > 🎬 **Watch / View Embedded Resource:** [https://dev.to/puppo](https://dev.to/puppo) ---