time: 2019.01.09
目录
- 例子
- 特性
- Generator.prototype.next() 方法参数
- Generator.prototype.throw()
- Generator.prototype.return()
- yield* 表达式
- 遍历 generator 遍历器
- 深入 generator 实例
- generator 与 协程、执行上下文
generator 是 es6 提供的一种异步变成的解决方案
generator是一个状态机,内部保存的是多个状态
关键词: generator 遍历器
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next();
hw.next();
hw.next();
hw.return();
- 函数名 * 开头:但是不限制写在具体位置,只要在函数名之前就行
- yield 表达式:函数执行暂停标志;返回 yield 后面表达式的值给函数 next 方法调用者;只能存在于generator 函数中
- generator 函数调用方式与普通函数一样,但是它不立即执行,返回的是一个指向内部状态的指针对象,也就是generator的遍历器对象,该对象实现了 Symbol.iterator 接口。每次获取下一个状态,需要调用这个遍历器对象的
next
方法,获取到的状态是一个对象,如{value: "hello", done: false}
- 因为 generator 函数执行返回的是一个实现了 iterator 接口的对象,可以直接使用 for of , ... 遍历了。可以用作函数延迟执行
2.2 特性中的第三点讲到调用 next 方法返回的是一个指针,指向一个对象,包含value和done属性。
返回的这个对象,是返回给的函数next方法调用者,比如 const obj = hw.next()
,这里返回的是 yield 语句后面的执行的结果;如果在函数内部执行 let hello = yield 'hello'
,这里的 hello 值是什么呢?
其实 yield
语句本身不返回值,它只是函数执行状态的一个标志,通过调用 next()
让其继续执行,比如上面的 hello 的值就是 undefined 。
如果想让 yield 返回值,也就是次状态执行返回的结果,是通过在 next 方法传参,比如
function* helloWorldGenerator() {
let hello = yield 'hello';
let world = yield hello + 'world';
return 'ending';
}
第一次执行 helloWorldGenerator 函数时,返回一个对象,也就是 generator 遍历器对象;
第二次执行时,调用 next 方法,才遍历第一次执行的遍历器,遇到 yield 语句,暂停执行,返回该 yield 语句后的表达式值给函数调用者,此时world的值为undefined;
第三次执行时,返回给函数调用者的是 world
;如果给此次调用 next() 方法传入参数,则该参数会被赋予给上个 yield 执行的返回结果,也就是 hello 会被设置值,比如 next('hello') ,那么第三次执行返回给函数调用者的值是 hello world
设计在原型上的方法,用于在实例抛出错误,在 generator 函数内部捕获错误
注意:设计在 generator 实例使用的 throw 方法和全局使用的 throw 命令不同,前者可以在函数内部捕获或外部捕获,后者只能在函数外部捕获
问:之前谈的都是在函数外部抛出错误,如果在 generator 内部发生了错误,又是什么结果呢?普通函数内部发生错误又是什么结果呢?
答:如果 generator 函数内部发生错误,不管有没有被捕获到,或者说外部、内部捕获,都不会继续执行。普通函数,即使内部捕获到了错误,也不会继续执行(经本地测试)
function *hello() {
try {
throw new Error('shit')
console.log('hello')
yield 'hello';
}catch (e) {
console.log('内部', e)
}
}
try {
var he = hello()
console.log(he.next()) // 內部 error ,{value: undefined, done: true}
console.log(he.next()) // {value: undefined, done: true}
} catch(e) {
console.log(e)
}
用于终结执行当前 generator 的遍历器对象,参数表示返回值,否则返回 undefined;
如果存在 try...finally ,那么如果执行 return 时,return 会在 finally 代码块执行完成之后执行
上面谈到的都是 yield 表达式,比如 let hello = yield 'hello'
, yield* 是什么呢?
问:如果在 generator 函数中调用普通函数,普通函数会执行,但是如果调用的是 generator 函数呢,会什么后果?
答:此时 generator 函数不会执行,只是获取到了 generator 的遍历器,得到的遍历器可以通过 next、for of 等实现遍历
yield*
: 只能在 generator 函数中使用,用于遍历 generator 遍历器,等同于把 generator 拆分成多个 yield 表达式。
function* concat(iter1, iter2) {
yield* iter1;
yield* iter2;
}
// 等同于
function* concat(iter1, iter2) {
for (var value of iter1) {
yield value;
}
for (var value of iter2) {
yield value;
}
}
目前能遍历 generator 遍历器的方法有4种:next, for of, yield*, ... 运算符
next
:返回一个对象,拥有 value 和 done 属性for of
:直接获取遍历器的 value...
: 直接获取遍历器的 valueyield* 表达式
:返回 yield 表达式
generator 形式上是一个普通函数,但是它却只是一个异步解决方案,浏览器并没有原生支持它,也就是说它不是函数的实例,它也不像 Promise 对象那样,由浏览器直接原生提供。它提供的语法只是方便开发者调用它,用以解决异步问题,实现的是一个状态机。
function* generatorTest () {
yield 'hello';
yield 'world';
}
function generatorTest() {
return _regenerator2.default.wrap(function generatorTest$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'hello';
case 2:
_context.next = 4;
return 'world';
case 4:
case 'end':
return _context.stop();
}
}
}, _marked, this);
}
可以看出
- generator 函数的执行,不会返回立即预定义的值,而是返回的一个包装器对象
- 返回的包装器对象需要遍历,才能返回预定义的值
- generator不被浏览器原生支持,也不是一个对象,它只是实现的一种机制
普通构造函数的 prototype 属性指向原型对象,那么 generator 函数的 prototype 属性呢?
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g // true
obj.hello() // 'hi!'
问:执行 g 函数返回 generator 遍历器,为什么该遍历器能够访问定义在 generator 函数prototype属性上的值呢?
答:返回的遍历器对象,之所以能够访问到定义在 generator 函数 prototype属性上的方法,因为 ES6 规定 这个遍历器对象是 generator 函数的实例,并且继承 generator 函数的 prototype 属性。所以,上面的 obj 就继承了 g 的 prototype 属性。
问:obj 作为实例,是不是可以认为 generator 可以用作构造器函数呢?
答:不行,构造函数实例化是通过 new 关键字实例的,这里的 obj 实例是通过执行 generator 函数生成的,不是通过 new 关键字生成的,而且 ES6 规定 也不能通过 new 关键字实例化 generator 函数,因为它不能用作构造函数。
因为不能用作构造函数,那么 generator 函数中的 this 代表什么呢?
function* generatorTest () {
console.log(this)
}
const test = generatorTest()
test.next() // undefined
直接输出 undefined
,它不是指向 window、global 等对象。为什么是 undefined
呢?
引用来自阮一峰 ECMAScript 6 入门中关于协程的介绍
协程是一种程序运行的方式,可以理解成
协作的线程
或协作的函数
。协程既可以用单线程实现,也可以使用多线程实现。单线程实现是一种特殊的子例程,多线程实现是一种特殊的线程。
传统函数执行上下文变化过程
当开始执行函数的时候,会创建一个函数的执行上下文,该上下文包含 函数作用域链
、变量对象v0
、this
,创建完成之后将其加入全局执行上下文栈中。函数执行完毕,将函数执行上下文从全局执行上下文栈中出栈。
generator函数执行上下文变化过程
在generator函数执行的时候,也为其创建执行上下文环境,不立即入全局执行上下文栈;当遍历 generator 遍历器的时候,将执行上下文环境对象压入全局执行上下文栈;执行时一旦遇到 yield
命令,则暂停执行函数,保留当前执行状态,将执行上下文环境出栈,但是不删除,此时可以做其他事情;当继续遍历的时候,又将generator执行上下文环境入栈,冻结的状态恢复。
学习 generator 可以搭配 redux-saga 学习