ES6 |Generator函数语法

在这里插入图片描述

Generator函数

Generator 函数是 ES6 提供的一种异步编程解决方案。Generator 函数在形式上具有两个特征:

一、function关键字与函数名之间有一个星号;

二、函数体内部使用yield语句,定义不同的内部状态

function* helloWorldGenerator() {
  yield 'hello';	//hello状态
  yield 'world';	//world状态
  return 'ending';	//结束
}

调用Generator函数后返回指向内部状态的指针对象,也就是遍历器对象。必须调用遍历器对象的next方法,才能使得指针移向下一个状态

var hw = helloWorldGenerator();
hw.next()// { value: 'hello', done: false }
hw.next()// { value: 'world', done: false }
hw.next()// { value: 'ending', done: true }
hw.next()// { value: undefined, done: true }

yield语句

yield语句就像暂停标志,每次next方法碰到yield就会暂停执行后面的操作,并将yield后面的表达式作为返回对象的value值;下一次调用next时再执行,直到遇到下一个yield,以此类推,碰到return时将后面的表达式作为返回对象的value值,如果return后面没有语句,那么返回对象的value就是undefined

function* gen() {
  yield  123 + 456;
}
gen().next() //{value: 579, done: false}
  • yield语句如果用在一个表达式之中,必须放在圆括号里面

    function* demo() {
      console.log('Hello' + (yield)); // OK
      console.log('Hello' + (yield 123)); // OK
    }
    
  • yield语句用作函数参数或放在赋值表达式的右边,可以不加括号

    function* demo() {
      foo(yield 'a', yield 'b'); // OK
      let input = yield; // OK
    }
    

与 Iterator 接口的关系

Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
};
function* gen(){}
var g = gen();	//函数返回的对象
g[Symbol.iterator]() === g	//执行Symbol.iterator后指向自己

状态可遍历

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

//1. 扩展运算符
[...numbers()] // [1, 2]

//2. Array.from 方法
Array.from(numbers()) // [1, 2]

//3. 解构赋值
let [x, y] = numbers();
x // 1
y // 2

//4. for...of 循环
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2
//for...of注意的点:
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
//解析:一旦next方法返回对象的done为true,循环就终止,切不包含该返回对象,所以最后结果没有6

next方法的参数

next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值

方法

  • Generator.prototype.throw():可以在函数体外抛出错误,然后在Generator函数体内捕获

    var g = function* () {
      try {yield;
      } catch (e) {
        console.log('内部捕获', e);
      }
    };
    
    var i = g();
    i.next();	
    //返回对象{value:undefined,done:false},如果不返回内部无法捕获到错误,最后只打印出“外部捕获 a”
    
    try {
      i.throw('a'); //用遍历器对象的throw方法抛错
      i.throw('b');
    } catch (e) {
      console.log('外部捕获', e);
    }
    
    // 内部捕获 a
    // 外部捕获 b
    //解析:遍历器对象i连续抛出两个错误,第一个被Generator函数体内的catch语句捕获,由于第一个错误已经被捕获,所以在函数外只捕获第二个错误b
    
    //注意:如果是全局的throw命令抛出错误,则只能被函数体外的catch捕获
    //如果Generator函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获
    //如果Generator函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行
    

    throw方法可以接受一个参数,该参数会被catch语句接收

    var g = function* () {
      try {
        yield;
      } catch (e) {
        console.log(e);
      }
    };
    
    var i = g();
    i.next();
    i.throw(new Error('出错了!'));	
    // Error: 出错了!(…)
    

    throw方法被捕获以后,会附带执行下一条yield语句,即执行一次next方法。

    var gen = function* gen(){
      try {
        yield console.log('a');
      } catch (e) {
        // ...
      }
      yield console.log('b');
    }
    
    var g = gen();
    g.next() // a
    g.throw() // b
    

    Generator函数体内抛出的错误,也可以被函数体外的catch捕获

    function* foo() {
      var x = yield 3;
      var y = x.toUpperCase();
      yield y;
    }
    
    var it = foo();
    it.next(); // { value:3, done:false }
    
    try {
      it.next(42);	//传入一个数值42,但数值没有toUpperCase(),内部会抛错
    } catch (err) {
      console.log(err);	//这时被函数外的catch捕获
    }
    

    一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefineddone属性等于true的对象

  • Generator.prototype.return():返回给定的值,并且终结遍历Generator函数

    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }
    var g = gen();
    
    //情况1:return不带参数
    g.next()        // { value: 1, done: false }
    g.return() // { value: undefined, done: true } //终止了
    
    //情况2:return带参数
    g.next()        // { value: 1, done: false }
    g.return('foo') // { value: "foo", done: true }	//value为改参数,终止
    g.next()        // { value: undefined, done: true }
    

    如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行

    function* numbers () {
      yield 1;
      try {
        yield 2;
        yield 3;
      } finally {
        yield 4;
        yield 5;
      }
      yield 6;
    }
    var g = numbers();
    g.next() // { value: 1, done: false }
    g.next() // { value: 2, done: false }
    g.return(7) // { value: 4, done: false } //调用后开始执行finally代码块
    g.next() // { value: 5, done: false }
    g.next() // { value: 7, done: true }	//等finally代码块执行完再返回
    

yield* 语句

背景:在 Generator 函数内部调用另一个 Generator 函数,默认情况下是没有效果的:

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

function* bar() {
  yield 'x';
  foo();	//调用另一个Generator 函数
  yield 'y';
}
for (let v of bar()){
  console.log(v);	
}
// "x"
// "y"

yield*作用:实现在 Generator 函数里执行另一个 Generator 函数。

function* bar() {
  yield 'x';
  yield* foo();	//在调用Generator 函数前加上yield*;
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

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

对比

function* bar() {
  yield 'x';
  yield foo();	//如果没有加*,将返回一个本身,在这里即遍历器对象
  yield 'y';
}
for (let v of bar()){
  console.log(v);
}
//x
//foo {...}
//y

这表明yield*返回的是一个遍历器对象

如果yield*后面跟着数据结构只要有Iterator接口,就会遍历

function* gen(){
  yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }

作为对象属性的Generator函数

//写法1:
let obj = {
  * myGeneratorMethod() {··· }
};
//写法2:
let obj = {
  myGeneratorMethod: function* () {// ···}
};

Generator函数的this

引入

function* g() {}	//一个Generator函数
g.prototype.hello = function () {	//添加原型
  return 'hi!';
};

let obj = g();
obj instanceof g // true	//表明obj是g生成的实例
obj.hello() // 'hi!'	//表明obj继承了g的原型

对比:如果把g当作普通的构造函数,不会生效。这是因为g返回的总是遍历器对象,而不是this对象,所以也不能通过new的方式跟g构造函数一起使用

function* g() {
  this.a = 11;
}
let obj = g();
obj.a // undefined

如何使Generator函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}

//1. 生成一个空对象
var obj = {};
//2. 用call绑定Generator函数内部的this
var f = F.call(obj);

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}
obj.a // 1
obj.b // 2
obj.c // 3

执行的是遍历器对象f,而生成的对象实例是obj,有没有办法将这两个对象统一呢?

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
//解决:将obj换成F.prototype,这样就可以统一用f既是遍历器对象,又是实例对象了
var f = F.call(F.prototype);
//再将F改成构造函数,就可以对它执行new命令了
function* gen() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}

function F() {
  return gen.call(gen.prototype);
}

var f = new F();

应用

  • 异步操作的同步化表达

    Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行,这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

    function* main() {
      var result = yield request("http://some.url");
      var resp = JSON.parse(result);
        console.log(resp.value);
    }
    
    function request(url) {
      makeAjaxCall(url, function(response){
        it.next(response);//next方法必须加上response参数,因为yield语句构成的表达式本身是没有值的
          //第二次next将response作为上一次yield的返回值,赋值给了resp
      });
    }
    
    var it = main();
    it.next();	//第一次next将执行语句准备好
    
  • 控制流管理

  • 部署Iterator接口

  • 作为数据结构

posted @ 2020-09-01 13:52  sanhuamao  阅读(82)  评论(0编辑  收藏  举报