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
yieldwill 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.iteratorproperty 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.iteratorproperty 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 thenext()method replaces the return value of the previousyield, and noyieldstatements are executed before the first call tonext(), passing arguments during the first call tonext()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-ofloop statements will directly ignore any return value specified by areturnstatement; as soon asdonebecomestrue, they immediately stop reading other values. The return value can be passed like an argument tonext()(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