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://www.walker-learn.xyz/archives/4334

(0)
Walker的头像Walker
上一篇 Mar 8, 2025 12:52
下一篇 Mar 8, 2025 12:51

Related Posts

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

    Node's package management and loading mechanisms: npm search xxx, npm view xxx, npm install xxx. Node.js file system operation APIs: Node.js's `fs` module provides synchronous (Sync) and callback/Promise-based asynchronous APIs for operating on local files and directories. Commonly used capabilities in daily development include reading, writing, appending, deleting, traversing directories, listening for changes, and so on. The following examples are based on C...

    Personal Nov 24, 2025
    31800
  • Love sports, challenge limits, embrace nature.

    Passion. In this fast-paced era, we are surrounded by the pressures of work and life, often neglecting our body's needs. However, exercise is not just a way to keep fit; it's a lifestyle that allows us to unleash ourselves, challenge our limits, and dance with nature. Whether it's skiing, rock climbing, surfing, or running, cycling, yoga, every sport allows us to find our inner passion and feel the vibrancy of life. Sport is a self-challenge. Challenging limits is not exclusive to professional athletes; it's a goal that everyone who loves sports can pursue. It can...

    Personal Feb 26, 2025
    1.5K00
  • Go Engineering Systematic Course 003 [Study Notes]

    grpc grpc grpc-go grpc seamlessly integrates protobuf protobuf. For those of you accustomed to using JSON and XML data storage formats, I believe most have never heard of Protocol Buffer. Protocol Buffer is actually a lightweight & efficient structured data storage format developed by Google, and its performance is truly much, much stronger than JSON and XML! protobuf…

    Personal Nov 25, 2025
    27400
  • Go Engineer Structured Course 011 [Learning Notes]

    Inverted Index for Queries
    1. What is an Inverted Index?
    An Inverted Index is a data structure used to quickly find documents containing specific terms. It is one of the core technologies of search engines.
    1.1 Basic Concepts
    Forward Index: Document ID → Document Content (list of terms)
    Inverted Index: Term → List of Document IDs containing the term
    1.2 Why is it called "Inverted"?
    An inverted index reverses the traditional relationship of "which terms a document contains" to "in which documents a term appears...

    Personal Nov 25, 2025
    28300
  • In-depth Understanding of ES6 009 [Learning Notes]

    Classes in JavaScript function PersonType(name){ this.name = name; } PersonType.prototype.sayName = function(){ console.log(this.name) } var person = new PersonType("Nicholas") p…

    Personal Mar 8, 2025
    1.3K00
EN
简体中文 繁體中文 English