In-depth Understanding of ES6 008 [Study Notes]

Iterators (Iterator) and Generators (Generator) are new features indispensable for efficient data processing. You will also find iterators present in other language features: the new for-of loop, the spread operator (...), and even asynchronous programming can use iterators. An iterator is a special object that has proprietary interfaces specifically designed for the iteration process. All iterator objects have a next() method, and each call returns a result pair...

Iterators (Iterator) and Generators (Generator)

This new feature is indispensable for efficient data processing, and you'll also find iterators in other language features: the new for-of loop, the spread operator (...), and even asynchronous programming can use iterators.

An iterator is a special object that has proprietary interfaces specifically designed for the iteration process. All iterator objects have a next() method, which returns a result object (in the form of: {value:true|false,next(){}}) each time it is called.

function createIterator(items){
  var i = 0;
  return {
    next:function(){
      let done = (i>=items.length);
      let value = !done?items[i++];undefined;
      return {
        done,
        value
      }
    }
  }
}
var iterator = createIterator([1,2,3])
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:3,done:false}
console.log(iterator.next()); // {value:undefined,done:true}
// 之后所有调用都会返回相同内容
// {value:undefined,done:true}

Generators

A generator is a function that returns an iterator, indicated by an asterisk (*) after the function keyword, and uses the new yield keyword within the function. The asterisk can be placed immediately after the function keyword or with a space in between.

// 生成器
function *createIteator(){
  yield 1;
  yield 2;
  yield 3;
}

// 生成器的调用方式与普通函数相同,只不过返回的是一个迭代器
let iterator = createIteator();

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

The most interesting part of a generator function is probably that the function automatically stops execution after each yield statement. After executing yield 1, the function will not execute any other statements until iterator.next() is called again to continue executing yield 2.

The yield keyword can return any value or expression, so you can add elements to an iterator in bulk through a generator function, or use the yield keyword in a loop.

The value after yield will be placed in the `value` property of the iterator's return value.

function *createIterator(items){
  for(let i=0;i<items.length;i++){
    yield items[i];
  }
}
let iterator = createIterator([1,2,3])

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}
// 之后所有的调用都会返回相同内容
console.log(iterator.next()) // {value:undefined,done:true}

Generator Function Expressions

let createIterator = function *(items){
  for(let i=0;i<items.length;i++){
    yield items[i];
  }
}
let iterator = createIterator([1,2,3])

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}
// 之后所有的调用都会返回相同内容
console.log(iterator.next()) // {value:undefined,done:true}

Generator Object Methods

let o={
  createIterator:function *(items){
    for(let i=0;i<items.length;i++){
      yield items[i];
    }
  }
}
// 或简写
let o1={
  *createIterator(items){
    for(let i=0;i<items.length;i++){
      yield items[i];
    }
  }
}

Iterable Objects and for-of Loops

Iterable objects have a Symbol.iterator property and are closely related to iterators. Symbol.iterator can return an iterator that operates on the associated object through a specified function. All ES6 collection objects (arrays, Set, and Map) and strings are iterable objects, and these objects all have default iterators.

Since generators assign a value to the Symbol.iterator property by default, all iterators created through generators are iterable objects.

Accessing Default Iterators

let values = [1,2,3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next()) // {value:2,done:false}
console.log(iterator.next()) // {value:3,done:false}
console.log(iterator.next()) // {value:undefined,done:true}

Since objects with a Symbol.iterator property have a default iterator, you can use it to check if an object is iterable.

function isIterator(object){
  return typeof object[Symbol.iterator] === "function";
}
console.log(isIterator([1,2,3])); // true
console.log(isIterator("Hello")); // true
console.log(isIterator(new Map())); // true
console.log(isIterator(new Set())); // true
console.log(isIterator(new WeakMap())); // false
console.log(isIterator(new WeakSet())); // false

Creating Iterable Objects

By default, developer-defined objects are not iterable, but if you add a generator to the Symbol.iterator property.

let collection = {
  items:[],
  *[Symbol.iterator](){
    for(let item of this.items){
      yield items;
    }
  }
}
collection.items.push(1)
collection.items.push(2)
collection.items.push(3)

for(let x of collection){
  console.log(x)
}

Built-in Iterators Collection Object Iterators

  • entries()
  • values()
  • keys()

String Iterators

var message = "A 𠮷 B";

for(let c of message){
  console.log(c)
}

NodeList Iterators

var divs = document.getElementByTagName("div");

for(let div of divs){
  console.log(div.id)
}

Spread Operator and Non-Array Iterable Objects

let set = new Set([1,2,3,3,3,4,5]),
array = [...set];
console.log(array); // [1,2,3,4,5]

let map = new Map([["name","Nicholas"],["age",25]]),
array = [...map];

console.log(array) // [["name","Nicholas"],["age",25]]

Advanced Iterator Features

You can use the return value of the iterator's next() method, or use the yield keyword inside the generator to produce values. If you pass an argument to the iterator's next() method, this argument's value will replace the return value of the previous yield statement inside the generator. To implement more advanced features like asynchronous programming...

function *createIterator() {
  let first = yield 1;
  let second = yield first + 2; 
  yield second +3;
}

let iterator = createIterator();

console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next(4)) // {value:6,done:false}
console.log(iterator.next(5)) // {value:8,done:false}
console.log(iterator.next()) // {value:undefined,done:true}

Any arguments passed during the first call to the next() method will be discarded. Since the argument passed to the next() method replaces the return value of the previous yield, and no yield statements are executed before the first call to next(), passing arguments during the first call to next() is meaningless.

Note the difference between yield xxx and let a = yield xxx.
After yield xxx executes, the value of the expression after yield is added to the value property of the iterated object, while the value of let a is the parameter passed in the previous next call.

function *createIterator(){
  let first = yield 1;
  let second;
  try{
    second = yield first+2; //
  }catch(ex) {
    second = 6;
  }
  yield second+3;
}

let iterator = createIterator();
console.log(iterator.next()) // {value:1,done:false}
console.log(iterator.next(4)) // {value:6,done:false}
console.log(iterator.throw(new Error('Boom'))) // {value:69,done:false}
console.log(iterator.next()) //{value:undefined,done:true}

Note: Calling the throw() method also returns a result object, just like calling the next() method. Since the generator internally caught this error, it will continue to execute the next yield statement.

Thus, next() and throw() are like two instructions for the iterator. Calling next() commands the iterator to continue execution, and calling throw() also commands the iterator to continue execution, but simultaneously throws an error. The subsequent execution process depends on the code inside the generator (whether it handles the error with try-catch).

Generator Return Statements

Since generators are functions, you can exit function execution early using a return statement. For the last next() method call, you can explicitly specify a return value. Previously, our last call always returned undefined. Just like in other functions, you can specify a return value with a return statement. In a generator, return indicates that all operations are complete, the done property is set to true, and if a return value is provided, the value property is set to that value. The return value specified by the return statement will only appear once in the returned object; in subsequent calls to the returned object, the value will be reset to undefined.

function *createIterator(){
  yield 1;
  return 42;
}
let iterator = createIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:42,done:true}
console.log(iterator.next()); // {value:undefined,done:true}

The spread operator and for-of loop statements will directly ignore any return value specified by a return statement; as soon as done becomes true, they immediately stop reading other values. The return value can be passed like an argument to next() (in delegated generators).

Delegating Generators

As follows: (add an * to the yield statement)

function *createNumberIterator(){
  yield 1;
  yield 2;
}

function *createColorIterator(){
  yield "red";
  yield "green";
}

function *createCombinedIterator(){
  yield *createNmberIterator()
  yield *createColorIterator()
  yield true
}
var iterator = createCombinedIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:"red",done:false}
console.log(iterator.next()); // {value:"green",done:false}
console.log(iterator.next()); // {value:true,done:false}
console.log(iterator.next()); // {value:undefined,done:true}

With this new feature of generator delegation, you can further leverage the return values of generators to handle complex tasks:

function *createNumberIterator(){
  yield 1;
  yield 2;
  return 3;
}

function *createRepeatingIterator(count){
  for(let i=0;i<count;i++){
    yield "repeat";
  }
}

function *createCombinedIterator(){
  let result = yield *createNumberIterator()
  yield *createRepeatingIterator(result); // 返回值赋值给result 
}
var iterator = createCombinedIterator()
console.log(iterator.next()); // {value:1,done:false}
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:"repeat",done:false}
console.log(iterator.next()); // {value:undefined,done:true}
// 数值3永远不会被返回,它只存在于生成器createCombinedIterator()的内部。如果想返回岀可以额外添加一条yield语句

yield * can also be directly applied to strings, for example, yield * "hello". In this case, the string's default iterator will be used.

Asynchronous Tasks

Asynchronizing complex tasks brings many code management challenges. Since generators support pausing code execution within a function, more uses of asynchronous processing can be explored. Simple callback handling is fine.

let fs = require('fs');
fs.readFile('config.json',function(err,contents){
  if(err){
    throw err;
  }
  doSomethingWith(contents);
  console.log("Done")
})

If you need to nest callbacks or serialize a series of asynchronous operations, things can become very complex. This is where generators and yield statements come in handy.

Simple Task Executor

Since executing a yield statement pauses the current function's execution and waits for the next next() method call, you can create a function that calls a generator to produce the corresponding iterator, thereby achieving asynchronous next() method calls without relying on callback functions (similar to async-await syntax sugar):

function run(taskDef){
  // 创建一个无使用限制的迭代器
  let task = taskDef();

  // 开始执行任务
  let result = task.next();

  // 循环调用next()
  function step(){
    // 如果任务未完成,则继续执行
    if(!result.done){
      result = task.next();
      step();
    }
  }

  // 开始迭代执行
  step()
}
run(function*(){
  console.log(1);
  yield;
  console.log(2);
  yield;
  console.log(3);

})

Passing Parameters to the Task Executor

function run(taskDef){
  // 创建一个无使用限制的迭代器
  let task = taskDef();

  // 开始执行任务
  let result = task.next();

  // 循环调用next()
  function step(){
    // 如果任务未完成,则继续执行
    if(!result.done){
      result = task.next(result.value);
      step();
    }
  }

  // 开始迭代执行
  step()
}

run(function*(){
  let value = yield 1;
  console.log(value) // 1
  value = yield value+3;
  console.log(value) // 4
})

Asynchronous Task Executor

function run(taskDef){
  // 创建一个无使用限制的迭代器
  let task = taskDef();

  // 开始执行任务
  let result = task.next();

  // 循环调用next()
  function step(){
    // 如果任务未完成,则继续执行
    if(!result.done){
      if(typeof result.value === 'funciton'){
        // 是函数传回调进去
        result.value(function(err,data){
          if(err){
            result = task.throw(err)
            return ;
          }
          result = task.next(data)
          step()
        })
      } else {
        result = task.next(result.value);
        step();
      }
    }
  }

  // 开始迭代执行
  step()
}

let fs = require("fs");
function readFile(filename){
  return function(callback){
    fs.readFile(filename,callback)
  }
}

run(function*(){
  let contents = yield readFile("config.json");
  doSomethingWith(contents);
  console.log("Done")
})

主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/4334

(0)
Walker的头像Walker
上一篇 Mar 10, 2026 00:00
下一篇 Mar 8, 2026 15:40

Related Posts

  • Node: In-depth Yet Easy to Understand (Shengsi Garden Education) 003 [Study Notes]

    WebSocket and SSE Overview WebSocket Basics Definition: WebSocket is a full-duplex connection upgraded after an HTTP handshake, allowing clients and servers to push data bidirectionally over the same TCP channel, eliminating the need for repeated polling. Handshake Process: The client initiates an HTTP request with the Upgrade: websocket header; The server responds with 101 Switching Protocols, and both parties agree...

    Personal Nov 24, 2025
    39700
  • Go Engineer System Course 012 [Study Notes]

    Integrate Elasticsearch in Go 1. Client Library Selection 1.1 Mainstream Go ES Clients olivere/elastic: Most comprehensive features, elegant API design, supports ES 7.x/8.x elastic/go-elasticsearch: Official client, lightweight, closer to native REST API go-elasticsearch/elasticsearch: Community-maintained offi…

    Personal Nov 25, 2025
    28100
  • Nuxt3: Beginner's Guide and Principles Introduction [Learning Notes]

    Nuxt 3: Getting Started and Principles 💡 What is Nuxt 3? Nuxt 3 is a full-stack frontend framework built on Vue 3 and Vite, supporting: Server-Side Rendering (SSR) Static Site Generation (SSG) Single-Page Applications (SPA) Building full-stack applications (with API support) Nuxt 3 is an "enhanced version" of Vue, helping you simplify project structure and development workflow. 🔧 Core Principles Feature How Nuxt Handles It ✅ Page Routing Automatic root...

    Personal Apr 6, 2025
    2.2K00
  • Node: Demystified (Shengsi Garden Education) 001 [Learning Notes]

    Node.js: Understanding it from an Asynchronous Programming Paradigm
    Node.js's Positioning and Core Philosophy
    Based on the V8 engine + libuv event-driven library, it brings JavaScript from the browser to the server side. It uses a single-threaded event loop to handle I/O, maximizing CPU time slices while waiting for I/O, making it particularly suitable for high-concurrency, I/O-intensive scenarios. "Don't block the main thread" is its design philosophy: try to offload time-consuming operations to the kernel or a thread pool, and callback results...

    Personal Nov 24, 2025
    34200
  • Waving to the world, embracing infinite possibilities 🌍✨

    Standing higher, seeing further. Life is like a series of tall buildings; we constantly climb upwards, not to show off the height, but to see a broader landscape. The two girls in the picture stand atop the city, with outstretched arms, as if embracing the boundless possibilities of the world. This is not merely a journey overlooking the city, but rather, a tribute to freedom and dreams. Brave Exploration, Breaking Boundaries. Everyone's life is an adventure; we are born free, and thus should explore unknown landscapes and experience more stories. Perhaps there will be challenges along the way, but it is precisely those moments of ascent...

    Personal Feb 26, 2025
    1.4K00
EN
简体中文 繁體中文 English