Generator
概念
Generator
函数是 ES6
提供的一种异步编程解决方案,
形式上,Generator
函数是一个普通函数,但是有两个特征
function
关键字与函数名之间有一个星号*
- 函数体内部使用
yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)
值得注意的是Generator
函数被调用后,不会执行,也不会返回任何结果,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,这就状态就好像正在进行中,等待执行命令.
实例
function* makeBread() {
yield '准备材料';
yield '做面包';
yield '放入烤箱';
return '完成,开吃';
}
var to = makeBread()
to.next() //{value: "准备材料", done: false}
to.next()//{value: "做面包", done: false}
to.next()//{value: "放入烤箱", done: false}
to.next()//{value: "完成,开吃", done: true}
//已经全部完成,再执行就会输出
to.next()//{value: undefined, done: true}
上述例子中,一共调用了5次next
方法
Generator
函数开始执行,直到遇到第一个yield
表达式为止。next
方法返回一个对象,它的value
属性就是当前yield
表达式的值准备材料
,done
属性的值false
,表示遍历还没有结束。Generator
函数从上次yield
表达式停下的地方,执行到下个yield
表达式为止.开始"做面包"
"放入烤箱"
Generator
函数执行到return
语句(如果没有return语句,就执行到函数结束)。next
方法返回的对象的value
属性,就是紧跟在return
语句后面的表达式的值(如果没有return
语句,则value
属性的值为undefined
),done
属性的值true
,表示遍历已经结束。
也就是说,每一次调用 next
方法,都会返回个有着value
和done
两个属性的对象,直到done == true
,表示是否遍历结束。
next
方法的参数
yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function* makeBread() {
var a, b, c
a = yield '准备材料'
if(a=="材料准备好了"){
b = yield '做面包'
}else{
b = yield '没材料怎么做面包'
}
if(b=="面包做好了"){
c = yield '放入烤箱'
}else{
c = yield '木得面包'
}
if(c=='放入烤箱')
{
return '完成,开吃'
}else{
return '吃空气咯'
}
}
var to = makeBread()
to.next() //{value: "准备材料", done: false}
to.next() //{value: "没材料怎么做面包", done: false}
to.next() //{value: "木得面包", done: false}
to.next() //{value: "吃空气咯", done: true}
//传入参数:
to.next() //{value: "准备材料", done: false}
to.next("材料准备好了")//{value: "做面包", done: false}
to.next("面包做好了")//{value: "放入烤箱", done: false}
to.next("放入烤箱")//{value: "完成,开吃", done: true}
如上面例子所示,通过next
方法的参数,就有办法在 Generator
函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator
函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。
for...of
循环
for...of
循环可以自动遍历 Generator
函数运行时生成的Iterator
对象,且此时不再需要调用next
方法。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
var arr = []
for (let v of foo()) {
arr.push(v)
}
arr // [1, 2, 3, 4, 5]
// 通过扩展运算符(...)、解构赋值和Array.from都能实现相同效果
[...foo()] // [1, 2, 3, 4, 5]
Array.from(foo())// [1, 2, 3, 4, 5]
上述例子中,6 并没有存取arr 数组之中,因为一旦返回对象的done
属性为true
,for...of
循环就会中止,且不包含该返回对象.
throw()方法
throw()
方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 done
及 value
两个属性的对象。
function* gen() {
while(true) {
try {
yield 42;
} catch(e) {
console.log("Error caught!");
}
}
}
var g = gen();
g.next(); // { value: 42, done: false }
g.throw(new Error("Something went wrong")); // "Error caught!"
Generator 函数的用法
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
var g = gen();
var result = g.next();
result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
})
上面代码中,首先执行 Generator
函数,获取遍历器对象,然后使用 next
方法(第二行),执行异步任务的第一阶段。由于 Fetch
模块返回的是一个 Promise
对象,因此要用 then
方法调用下一个next
方法。
可以看到,虽然 Generator
函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
所以,ES2017
标准引入了 async
函数,使得异步操作变得更加方便。
async
如何异步操作,将在后面的文章中描述.