- Generator函数简介
- Generator和iterator的关联
- yield语句
- yield* 语句
- next方法
- throw方法
- return方法
- Generator函数中的this
- Generator函数的应用
Generator函数简介
基本概念
Generator
函数是ES6
提供的一种异步编程解决方案
语法上理解
Generator
函数是一个状态机,封装了多个内部状态(通过yield
和return
来定义)。Generator
函数还是一个遍历器生成对象,执行以后会返回一个遍历器对象,可以遍历函数内部的状态
定义形式上理解
Generator
函数是一个普通的函数,有几个特征:
function
关键字和函数名称之间存在一个*
号- 函数体内部使用
yield
语句和return
语句(结束执行),定义不同的内部状态 - 函数调用以后,并不执行,返回的也不是函数的运行结果,而是一个遍历器对象,指向函数的开始位置,需要通过迭代器对象的
next
函数改变内部的状态
基本示例
function* gen() {
yield 'hello';
yield 'world';
return 'ending';
}
var it = gen();
console.log(it.next()) // {value: 'hello', done: false}
console.log(it.next()) // {value: 'world', done: false}
console.log(it.next()) // {value: 'ending', done: true}
console.log(it.next()) // {value: 'undefined', done: true}
// 作为对象属性定义Generator函数
var obj = {
* gen() {
yield 1;
},
foo: function* () {
yield 2;
}
// 以上两种方式等效
}
Generator和iterator的关联
因为
Generator
函数调用后会返回一个遍历器对象,该对象存在Symbol.iterator
属性,执行后的结果等于自身
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen()
console.log(g[Symbol.iterator]() === g) // true
可以把
Generator
函数赋值给对象的Symbol.iterator
属性,从而使对象拥有iterator
接口
var obj = {
name: 'xxx',
age : 12
}
obj[Symbol.iterator] = function* gen() {
yield 1;
yield 2;
yield 3;
}
for(let i of obj) {
console.log(i) // 1, 2, 3
}
yield语句
yield
语句是Generator
函数的暂停标志,
yield语句使用的注意事项
- 只能在
Generator
函数中使用,不能再普通函数中使用
function* gen() {
yield 'hello'
}
gen() // success
function nGen() {
yield 1 // SyntaxError: Unexpected number
}
yield
语句如果用在一个表达式之中,必须放在圆括号里面
function* gen() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
yield
语句用作函数参数或赋值表达式的右边,可以不加括号
foo(yield 'a', yield 'b'); // success
let input = yield; // success
yield语句和return语句的比较
yield
语句和return
语句都会返回后面表达式的值yield
语句拥有位置记忆功能,会从上一次暂停的位置继续向后执行;但是return
语句只能执行一次- 正常函数通过
return
语句只能返回一个值,但是Generator
函数可以通过yield
语句返回一系列的值 for..of
循环Generator
的遍历器对象时,会遍历yield
语句后表达式的值,但是不会遍历return
后面表达式的值
yield* 语句
Generator
函数中无法嵌套调用Generator
函数,需要通过yield*
语句来实现此功能,yield*
语句不算一个状态,不影响next
函数的执行与暂停
yield* + 无return的Generator函数
如果
yield*
后面的Generator
函数没有return
语句,等同于for...of
遍历
function* foo() {
yield 'a';
yield 'b';
}
function* gen() {
yield 'x';
yield * foo();
yield 'y';
}
// 等同于
// function* gen() {
// yield 'x';
// for (let v of foo()) {
// yield v;
// }
// yield 'y';
// }
vatr it = gen()
console.log(it.next()) // {value: 'x', done: false}
console.log(it.next()) // {value: 'a', done: false}
console.log(it.next()) // {value: 'b', done: false}
console.log(it.next()) // {value: 'y', done: true}
yield* + 有return的Generator函数
如果
yield*
后面的Generator
函数存在return
语句,需要用var value = yield* iterator
的形式获取return
语句的值
return
语句的值可接受,也可以不接受,不是必须的
function* genFuncWithReturn() {
yield 'a';
yield 'b';
return 'The result';
}
function* logReturned(genObj) {
let result = yield* genObj;
console.log(result);
}
[...logReturned(genFuncWithReturn())]
// The result
// ['a', 'b']
yield* + array/string/Set/Map/nodeList/arguments
任何数据结构只要有
Iterator
接口,就可以被yield*
遍历
// yield* 数组
function* gen() {
yield 1;
yield* [2, 3, 4];
yield 5
}
for(let i of gen()) {
console.log(i) // 1, 2, 3, 4, 5
}
// yield* 字符串
function* gen() {
yield 'Hello';
yield* 'Today';
yield 'world';
}
for(let i of gen()) {
console.log(i) // 'Hello', 'T', 'o', 'd', 'a', 'y', 'world'
}
next方法
next方法的运行逻辑
next
方法运行时,遇到yield
语句,就暂停执行后面的内容,直到下一次再次调用;yield
后面的表达式的值,会被作为next
方法返回的对象的value
值next
方法如果遇不到新的yield
语句,就会一直运行到函数结束;- 如果存在
return
语句,return
后面表达式的值就是返回对象的value
属性的值,如果不存在,value
的值就是undefined
next方法的参数
yield
语句本身没有返回值(undefined
),如果需要其返回某个值,可以在调用next
方法的时候传入参数,此参数会作为上一个yield
语句的返回值
// 实例一
function* fn() {
for(let i = 0; true; i++) {
let res = yield i;
if (res) {
i = -1
}
}
}
var it = fn()
it.next() // 启动遍历器对象 {value: 0, done: false}
it.next() // 第二次执行 未传参 res的值为undefined {value: 1, done: false}
it.next() // 第三次执行 res的值为true i重置为-1 {value: 0, done: false}
// 实例二
function* gen(x) {
var y = 2 * (yield (x + 1))
var z = yield (y / 3)
return (x + y + z)
}
var it = gen(5)
it.next() // {value: 6, done: false}
it.next() // 没有传参 y的值为NaN {value: NaN, done: false}
it.next() // return语句 done为true 没有传参 z的值为undefined {value: NaN, done: true}
// 实例三
function* gen(x) {
var y = 2 * (yield (x + 1))
var z = yield (y / 3)
return (x + y + z)
}
var it = gen(5) // x = 5
it.next() // {value: 6, done: false}
it.next(12) // y = 24 {value: 8, done: false}
it.next(13) // z = 13 {value: 42, done: true}
在第一次调用
next
方法的时候,传入的参数无法生效,因为第一个next
方法默认用来启用遍历器对象,不需要携带参数
如果需要在第一次调用next
方法时传入的参数就生效,可以对Generator
函数再进行一层封装
function genWrapper(fn) {
return function (...args) {
let it = fn(...args)
it.next()
return it
}
}
var gen = genWrapper(function* (...args) {
console.log(args)
var one = yield 1;
console.log(one)
var two = yield 2;
console.log(two)
var three = yield 3;
console.log(three)
})
gen('hello world!') // 内部默认调用一次next函数 ['hello world!]
gen('hello world!').next(111) // 内部已经调用过next函数 再次调用传入的参数就是上次yield的返回值 one
throw方法
Generator
函数生成的遍历器对象,都有throw
方法,定义在Generator.prototype
原型链式上。
throw
可以在外部调用,抛出异常,Generator
函数体内进行try...catch
异常捕获,此过程会默认执行一次next
方法。
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var it = gen();
it.next() // a
it.throw() // b 默认调用一次next方法
it.next() // c
Generator
函数内部的try...catch
只能捕获第一次throw
方法抛出的异常,剩余的throw
方法抛出的异常,都会被抛出函数体外,由外部进行处理。
try {
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e) // success 内部捕获 第一次抛出错误
}
};
var i = g();
i.next();
i.throw('第一次抛出错误')
i.throw('第二次抛出错误');
} catch(e) {
console.log('外部捕获', e) // success 外部捕获 第二次抛出错误
}
如果
Generator
函数内部没有进行异常捕获,就会被外部的catch
代码捕获,如果两者都没有,代码块报错,中断运行。
function* gen(){
yield 123;
throw '错误'
yield 456;
}
var it = gen()
console.log(it.next()) // success {value: 123, done: false}
console.log(it.next()) // error Uncaught 错误
Generator
函数内部抛出的异常没有捕获的话,默认运行结束,再次调用next
方法,返回对象为{value: undefined, done: true}
function* gen(){
yield 123;
throw '错误'
yield 456;
}
var it = gen()
console.log(it.next()) // success {value: 123, done: false}
try {
console.log(it.next())
} catch(e) {
console.log(e) // success 错误;第二次执行 throw异常 被外部catch捕获
}
console.log(it.next()) // success { value: undefined, done: false }
return方法
Generator
函数生成的遍历器对象,都有return
方法,定义在Generator.prototype
原型链式上。
return
函数的作用是:结束Generator
函数的状态变更,即结束遍历return
方法可以接收一个参数,作为返回对象的value
值,不传默认为undefined
return
方法会推迟到try..finally
中finally
代码块执行完以后再执行
function* gen() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.log(e)
yield 4;
yield 5;
} finally {
yield 6;
yield 7;
yield 8;
}
}
var it = gen();
console.log(it.next()) // success {value: 1, done: false}
console.log(it.throw('抛出异常')) // success {value: 4, done: false}
console.log(it.return(7)) // success {value: 6, done: false}
console.log(it.next()) // success {value: 7, done: false}
console.log(it.next()) // success {value: 8, done: false}
console.log(it.next()) // success {value: 7, done: true}
Generator函数中的this
ES6
规定Generator
函数执行返回的遍历器是它的实例,也继承了Generator
函数原型链prototype
对象上的方法
function* gen() {
yield 1;
yield 2;
}
gen.prototype.sayHello = function () {
console.log('Hello')
}
var g = gen()
console.log(g instanceof gen) // true
g.sayHello() // 'Hello'
Generator
函数不能当做普通构造函数使用,因为它永远返回遍历器对象,而不是this
对象,也不能和new
命令联用
// this 指向
function* gen() {
this.a = 'a'
}
var it = gen()
console.log(it.a) // undefined
// new 命令
function* Foo() {
yield this.a = 1;
yield this.b = 2;
}
new Foo() // TypeError: F is not a constructor
获取this示例的方法
利用
call
改变this
的指向,指向一个外部定义好的空对象,此种形式,遍历器对象和this
对象不是同一个示例
var obj = {}
function* Gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var it = Gen.call(obj)
console.log(obj) // {a : 1, b : 2, c : 3}
console.log(it.next()) // Object {value: 2, done: false}
console.log(it.next()) // Object {value: 3, done: false}
console.log(it.next()) // Object {value: undefined, done: false}
利用原型链,将
this
指向Generator
函数,所有定义的变量和方法都会挂载到原型链上,而遍历器对象,也就是Generator
函数的实例对象同样可以访问原型链上的内容
function* Gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
var it = Gen.call(Gen.prototype)
console.log(it.next()) // Object {value: 2, done: false}
console.log(it.next()) // Object {value: 3, done: false}
console.log(it.next()) // Object {value: undefined, done: false}
console.log(it.a) // 1
console.log(it.b) // 2
console.log(it.c) // 3
利用原型链的方式,可以对
Generator
函数进行二次封装,将其改造成构造函数
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
console.log(f.next()) // Object {value: 2, done: false}
console.log(f.next()) // Object {value: 3, done: false}
console.log(f.next()) // Object {value: undefined, done: true}
console.log(f.a) // 1
console.log(f.b) // 2
console.log(f.c) // 3
Generator函数的应用
异步操作的同步化表达
由于next
方法的参数,表达的是上一次yield
语句的返回值,同时Generator
函数内部状态的改变,需要通过next
方法来控制,可以将异步操作同步化
// seTimeout 模拟异步操作
// 先获取用户数据 再根据用户数据获取订单数据 再根据订单数据 获取商品数据
function getUsers() {
setTimeout(() => {
var data = '用户数据'
it.next(data)
}, 1000)
}
function getOrders() {
setTimeout(() => {
var data = '订单数据'
it.next(data)
}, 1000)
}
function getGoods() {
setTimeout(() => {
var data = '商品数据'
it.next(data)
}, 1000)
}
function* gen() {
var users = yield getUsers()
console.log(users)
var orders = yield getOrders()
console.log(orders)
var goods = yield getGoods()
console.log(goods)
}
var it = gen()
it.next()
控制流管理
可以利用Generator
函数将嵌套操作直线化(不适用于存在异步操作的情况)
function* longRunningTask(value1) {
try {
var value2 = yield step1(value1); // step1中调用next,并将执行结果作为参数,
var value3 = yield step2(value2); // step2中调用next,并将执行结果作为参数,
var value4 = yield step3(value3); // step3中调用next,并将执行结果作为参数,
var value5 = yield step4(value4); // step4中调用next,并将执行结果作为参数,
// Do something with value4
} catch (e) {
// Handle any error from step1 through step4
}
}
var it = longRunningTask()
it.next()
部署iterator接口
因为Generator
函数返回的是一个遍历器对象,可以进行for...of
遍历,所以可以利用此特性为任意对象部署iterator
接口
function* objEntries(obj) {
let keys = Object.keys(obj)
for(let i of keys) {
yield [i, obj[i]]
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of objEntries(myObj)) {
console.log(key, value);
// foo 3
// bar 7
}