JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
108: Generators and Iterators in JavaScript
I was working on a project last week where I had to process a massive set of log entries. The problem is that loading a hundred thousand lines into an array just to loop over them is a great way to crash a browser tab or eat up all your server's RAM. I needed a way to say, "Give me the next item when I'm ready for it," rather than "Give me everything right now."
The struggle with manual state
My first instinct was to build a simple "cursor" object. I wanted something that remembered where it left off in the data set. I tried writing it like this:
const logs = ['Log 1', 'Log 2', 'Log 3', 'Log 4'];
const logCursor = {
position: 0,
next() {
if (this.position < logs.length) {
return { value: logs[this.position++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
console.log(logCursor.next()); // { value: 'Log 1', done: false }
console.log(logCursor.next()); // { value: 'Log 2', done: false }
This actually works. This is essentially what a JavaScript Iterator is: an object with a next() method that returns an object with value and done. But honestly? It's a pain to write. I have to manually track the position, handle the boundary check, and return that specific object structure every single time. It feels like I'm doing the engine's job for it.
Finding a better way to pause
I remember hearing about Generators, which are basically functions that can be paused and resumed. The magic happens with a * after the function keyword and the yield keyword. Let's try rewriting that log cursor using a generator to see if it cleans up the noise.
function* logGenerator(data) {
for (const entry of data) {
yield entry;
}
}
const gen = logGenerator(['Log 1', 'Log 2', 'Log 3', 'Log 4']);
console.log(gen.next()); // { value: 'Log 1', done: false }
console.log(gen.next()); // { value: 'Log 2', done: false }
Look at that. The yield keyword is doing all the heavy lifting. When the code hits yield, it literally freezes the function's execution state—including all local variables—and spits the value out. When I call next() again, it wakes back up exactly where it left off.
Wait, why bother with the .next() syntax?
You might be looking at this and thinking, "Why would I ever use gen.next() when I can just use a for...of loop?" That's a fair question. The reason is that generators are iterable. Since they follow the iterator protocol (returning that value/done object), they plug directly into JavaScript's built-in loops.
function* idGenerator() {
let id = 1;
while (true) { // Yes, an infinite loop!
yield `user_${id++}`;
}
}
const ids = idGenerator();
// I can take just what I need without crashing the program
console.log(ids.next().value); // "user_1"
console.log(ids.next().value); // "user_2"
// Or use it in a loop with a break condition
for (const id of ids) {
console.log(id);
if (id === 'user_5') break;
}
The infinite loop here is actually a feature, not a bug. Because the generator pauses at yield, it doesn't lock up the main thread. It only computes the next value when specifically asked. This is incredibly powerful for things like generating unique IDs, reading a file line-by-line, or implementing a custom pagination system where you don't know how many pages exist until you actually hit the last one.
📋 Practical Task
Building a Smart Batch-Processing Sequence
You are building a system that processes a list of orders, but your API can only handle them in batches of 3. Instead of slicing the array manually, create a generator function called batchProcessor.
Requirements:
- The function should take an array of items as an argument.
- It should
yieldan array containing up to 3 items at a time. - It should stop once all items have been yielded.
Test your code with this setup:
const orders = ['Order 1', 'Order 2', 'Order 3', 'Order 4', 'Order 5', 'Order 6', 'Order 7'];
const processor = batchProcessor(orders);
console.log(processor.next().value); // Expected: ['Order 1', 'Order 2', 'Order 3']
console.log(processor.next().value); // Expected: ['Order 4', 'Order 5', 'Order 6']
console.log(processor.next().value); // Expected: ['Order 7']
console.log(processor.next().done); // Expected: true
There are no comments for now.