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
属性等于undefined
、done
属性等于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接口
-
作为数据结构