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 8, 2025 12:52
下一篇 Mar 8, 2025 12:51

Related Posts

  • Node In-depth and Easy to Understand (Sheng Siyuan 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
    23100
  • In-depth Understanding of ES6 011 [Learning Notes]

    Promises and Asynchronous Programming

    Because the execution engine is single-threaded, it needs to track the code that is about to run. This code is placed in a task queue. Whenever a piece of code is ready to execute, it is added to the task queue, and whenever a piece of code in the engine finishes execution, the event loop executes the next task in the queue. A Promise acts as a placeholder for the result of an asynchronous operation. It doesn't subscribe to an event or pass a callback function to the target function. Instead, it allows the function to return a Promise, like this...

    Personal Mar 8, 2025
    1.1K00
  • Go Engineer Systematic Course 001 [Study Notes]

    Transitioning: Reasons for a rapid, systematic transition to Go engineering:
    To improve CRUD operations.
    To gain experience with self-developed frameworks.
    For colleagues aiming to deepen technical expertise, specializing and refining requirements.
    To advance engineering practices, developing good coding standards and management capabilities.

    The Importance of Engineering

    Expectations for Senior Developers:
    Good code standards.
    Deep understanding of underlying principles.
    Familiarity with architecture.
    Familiarity with K8s basic architecture.
    Expanding knowledge breadth and depth, and a standardized development system.

    Four Major Stages:
    Go language fundamentals.
    Microservice development (e-commerce project practical experience).
    Self-developed microservices.
    Self-developed, then re...

    Personal Nov 25, 2025
    28100
  • Go Engineer Systematic Course 008 [Study Notes]

    Orders and Shopping Cart
    First, copy the service code framework of 'srv' from the inventory service, then find and replace the corresponding name (order_srv).

    Fundamentals of Encryption Technology
    Symmetric Encryption
    Principle:
    Uses the same key for encryption and decryption.
    Like a single key that can both lock and unlock a door.
    Fast encryption speed, suitable for large data transfers.
    Use cases:
    Local file encryption
    Database content encryption
    Content encryption during large data transfers
    Fast communication between internal systems...

    Personal Nov 25, 2025
    19800
  • Fearless forward, fist power unleashed.

    Striving is an attitude. Life is like a competition with no shortcuts; only through continuous training, breaking through, and surpassing oneself can one stand on their own stage. This is not merely a contest, but rather a self-awakening—daring to face the fight, daring to challenge, and daring to become a stronger version of oneself. The Spirit of Striving in Sports. Whether it's boxing, running, or strength training, every punch thrown, every bead of sweat, every moment of gritting one's teeth and persevering, is a tempering of both body and mind. Striving is not merely a confrontation, but an attitude—facing challenges without backing down; facing failure, ...

    Personal Feb 26, 2025
    1.3K00
EN
简体中文 繁體中文 English
欢迎🌹 Coding never stops, keep learning! 💡💻 光临🌹