Mastering Iterators and Generators in JavaScript: A Beginner's Guide

📆 · ⏳ 4 min read · · 👀

Introduction

Generators and iterators are features introduced in ECMAScript 6 (ES6), which is also known as ES2015. These features provide a new way to iterate over collections of data and to generate values on the fly.

Generators allow you to define functions that can be paused and resumed, while iterators provide a standardized way to traverse collections of data. Let’s dive deeper into these concepts.

Generators

A generator function is a special type of function that can be paused and resumed at any time, allowing you to generate a sequence of values on the fly.

To define a generator function, you use the function* syntax (the asterisk distinguishes it from a regular function).

Inside the generator function, you use the yield keyword to pause the function and return a value to the caller.

For example, let’s define a simple generator function that generates a sequence of numbers:

function* numberGenerator() {
yield 1;
yield 2;
yield 3;
}

In this example, when we call the numberGenerator function, it will return an iterator that we can use to iterate over the sequence of numbers. To do this, we use the next method of the iterator, like this:

const iterator = numberGenerator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

As you can see, each time we call the next method of the iterator, the generator function resumes execution and returns the next value in the sequence.

Iterators

An iterator is an object that provides a standard way to traverse a collection of data. In JavaScript, iterators are defined by the Iterable and Iterator protocols.

The Iterable protocol defines a method that returns an Iterator object, while the Iterator protocol defines a next method that returns an object with two properties: value and done.

To make an object iterable, you define a method on the object that returns an iterator. This method is called [Symbol.iterator], and it should return an object that conforms to the Iterator protocol.

For example, let’s define a simple iterable object that generates a sequence of numbers:

const numberIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};

In this example, we define a generator function inside the [Symbol.iterator] method to generate the sequence of numbers. We can now use a for...of loop to iterate over the sequence:

for (const number of numberIterable) {
console.log(number);
}
// Output: 1, 2, 3

Real life example

Suppose you are building a web application that allows users to upload and manage images. You want to implement a feature that automatically resizes the images to a certain size before saving them to your server.

To accomplish this, you can use generators and iterators to process the images in a stream-like fashion, allowing you to efficiently resize and save the images one by one.

First, you create a generator function that retrieves the uploaded images from the client-side and yields each image as a data URI

function* getImageDataURIs() {
const imageFiles = document.querySelector('#image-input').files;
for (const file of imageFiles) {
const reader = new FileReader();
reader.readAsDataURL(file);
yield new Promise((resolve) => {
reader.onload = () => {
resolve(reader.result);
};
});
}
}

Next, you create an iterator function that retrieves each image data URI from the generator and resizes it using a library like sharp or whatever is your preferred library/API

async function* resizeImages() {
const imageURIs = getImageDataURIs();
for await (const uri of imageURIs) {
const imageBuffer = Buffer.from(uri.split(',')[1], 'base64');
const resizedImageBuffer = await sharp(imageBuffer)
.resize({ width: 500 })
.toBuffer();
yield resizedImageBuffer;
}
}

Finally, you create a function that saves each resized image to your server

async function saveResizedImages() {
const resizedImages = resizeImages();
for await (const imageBuffer of resizedImages) {
await fetch('/api/images', {
method: 'POST',
body: imageBuffer,
headers: {
'Content-Type': 'image/jpeg',
},
});
}
}

By using a generator and an iterator, you can easily process each uploaded image in a stream-like fashion, without having to load all the images into memory at once.

This can be especially useful if you are dealing with a large number of images, as it allows you to process them efficiently and without running into memory issues.

Conclusion

Generators and iterators are powerful tools that provide a new way to work with collections of data in JavaScript. Generators allow you to generate a sequence of values on the fly, while iterators provide a standardized way to traverse collections of data.

By using these features, you can write more efficient and scalable code in JavaScript.

You may also like

  • # javascript

    Write Secure JavaScript Applications

    Dive into the realm of writing secure JavaScript applications. Uncover practical strategies to shield your web apps from XSS and CSRF vulnerabilities, ensuring robust and safe software in an interconnected world.

  • # javascript

    Multi-Threaded JavaScript with Web Workers

    Are you tired of slow and unresponsive web applications? Do you want to improve the performance of your JavaScript code without sacrificing user experience? Look no further than JavaScript's Web Workers API. In this article, we'll explore the basics of web workers and how they can help you create multi-threaded web applications.

  • # javascript

    Asynchronous JavaScript Programming: A Guide to Promises, Async/Await, and Generators

    Asynchronous programming is essential in JavaScript to handle time-consuming operations and provide a better user experience. This article will provide a detailed guide to mastering asynchronous JavaScript programming with Promises, Async/Await, and Generators.