Generator生成器

什么是生成器?

ES6的新语法,并不直接执行逻辑,而是生成一个对象--迭代器,通过调用这个对象的next()方法,来一步步向后走。执行完一个yield关键字就会停止,等待下一次next()调用。

funtion* myLogin(){
  yield 2;
  yield 5;    
return 10; }
var it = myLogin();
it.next(); // 2
it.next(); // 5

当执行到return语句时,迭代器的迭代已经结束。如果生成器中没有return语句,则返回对象的value值为undefined

return 10; 
// {value: 10, done: true}

yield表达式如果用在另一个表达式中,必须放在圆括号中

console.log('Hello' + (yield 12))
console.log('Hello' + yield 12) //报错

next()的参数问题

next()函数是可以接受一个参数的,其作为上一次yield的返回值。但第一次使用next()函数时,是没有上一次的yield的,并且实际上是来开启遍历器对象的,传参是没有效果的。

在有些情况下,从第二次使用next()开始传入参数就很有必要了

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }

在以上代码中,第一次next()得到值6,下一次next()如果不传参数,x+1的值就被认为是undefined,后面继续执行结果都为NaN。但是如果传入了参数,比如例子中的12,也就是将x+1赋值为12,y=24,第二次next()的value为8。第三次传入13,y/3的值、z的值就为13,return结果就是5+24+13=42

如果一定想在第一次调用next()时就传参并生效,则需要在生成器外部包一个函数

function wrapper(genFunc){
  return funtion(...arg){
     var gen = genFunc(...arg);
     gen.next();    
     return gen;  
  }  
}
wrapper(funtion*(){
   console.log(`first input ${yield}`) 
   return 'Done'; 
})

wrapper().next('lalala')

因为在调用wrapper函数时,内部返回的函数就已经开启了迭代器,所以可以第一次next()时就传参且生效

for...of...

for...of...循环可以自动遍历Generator函数生成的迭代器,不需调用next()方法

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
// 1 2 3 4 5 }

当遍历器返回的done为true,for..of..就会中止

除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

Generator.prototype.throw

返回的遍历器对象,都有一个throw()方法,当执行出现错误时,抛出异常。这个异常可以被生成器内部的catch捕捉,也可以被全局catch捕捉。外部的全局throw也可以被内部的catch捕捉。如果生成器抛出的异常没有catch捕捉,那么遍历器会中止执行。

Generator.prototype.return

返回的遍历器对象,都有一个return()方法。当调用return()方法时,返回的value值就为return的值,done为true,也就是遍历器会中止执行。如果return()中不传参数,返回的value值为undefined

funtion* gen(){
  yield 1;
  yield 2;
  yield 3;  
}
var it = gen();
it.next(); // {value : 1, done : false}
it.return(6); // {value : 6, done : true}

当生成器函数中存在try...finally..代码块,且正好执行到try代码块时,return()会直接开始执行finally中的代码,执行完后返回传入的值,遍历器中止

function* gen() {
            try{
                yield 1;
                yield 2;
            }finally{
                yield 3;
                yield 4;
            }
        }
        var it = gen(); 
        it.next();// {value:1,done:false}
        it.return(7); {value:3,done:false
    it.next();// {value:4,done:false}
     it.next();// {value:7,done:true}

yield*

如果在一个生成器函数中调用另一个生成器函数,需要在前者函数内部对后者进行遍历

function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  // 手动遍历 foo()
  for (let i of foo()) {
    console.log(i);
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}

ES6中提供yield*解决

function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

for (let v of bar()){
  console.log(v);
} 
// x a b y

如果使用的是yield而不是yield*返回的就是一个遍历器对象,而不是遍历的结果

当使用yield*遍历的生成器函数内部没有return语句时,就相当于使用for...of...遍历生成器函数。如果有return语句,就需要使用一个变量来接收return的值

只要被遍历的对象有iterator接口,就可以使用yield*进行遍历

Generator中的this

生成器返回的都是遍历器对象,可以得到生成器原型上的属性和方法。但是this并不指向这个遍历器对象。

异步场景下的使用

function myAjax(){
        return fetch('https://img.alicdn.com/imgextra/i4/745475881/O1CN01VtOOnR1tJXl7Cnmcy_!!745475881.png');
    }
    function genRunner(genFunc){
        return new Promise(function(resolved, rejected){
            var it = genFunc();
            var innerRunner = function (data) {
                var val = it.next(data)
                if (val.done) {
                    resolved(val.value)
                    return
                }
                if (val.value) {
                    val.value.then(data => {
                        innerRunner(data)
                    })
                }else{
                    innerRunner(val.value)
                }
            }
            innerRunner();
        }
    )};


    genRunner(function*() {
        // generator函数中发起异步请求
        var serverData = yield myAjax();
        console.log('MyLogic after myAjax');
        console.log('serverStatus:%s',serverData.status);
    }).then(function (message) {
        console.log(message)
    })

在以上代码中,执行genRunner函数,生成器作为参数传入。genRunner函数中首先调用生成器得到迭代器it,然后调用innerRunner方法。第一次调用时,执行至生成器yield关键字结束,即发起了一个ajax请求,返回了一个promise对象。在innerRunner中,val.value就是这个promise对象,通过.then执行next(),将响应结果传到生成器中,就拿到了最后的状态码。此时innerRunner中的val为{value:undefined, done:true},执行最后的resolved函数,value为undefined,因此输出undefined

posted @ 2020-04-16 21:08  ashen1999  阅读(146)  评论(0编辑  收藏  举报