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 如何异步操作,将在后面的文章中描述.