一、Generator函数简介
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
ES6定义generator标准时借鉴了Python的generator的概念和语法。
1、理解Generator函数
Generator函数有多种理解角度。
function* gen() {
yield 1;
yield 2;
yield 3;
return 'ending';
}
let g = gen();
// "Generator { }"
上面代码定义了一个Generator函数 gen,它内部有三个yield 表达式,即该函数有四个状态:1,2,3和return语句(结束执行)。
(1)语法
状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象。
也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
(2)形式
Generator 函数是一个普通函数,但是有两个特征。
一是,function
关键字与函数名之间有一个星号;
yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”)。
2、next方法
Generator.prototype.next()方法用于恢复执行,返回值是包含value和done属性的对象。
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。
next方法,使得指针移动向下一个状态。
next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。
yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
function* gen() {
yield 1;
yield 2;
yield 3;
return 'ending';
}
let g = gen();
// "Generator { }"
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: 'ending', done: true }
value属性是当前yield表达式的值,done属性值为false,表示遍历未结束。
yield表达式停止的地方开始,一直执行到下一个yield表达式。
第四次调用,此时 Generator 函数已经运行完毕,next
方法返回对象的value
属性为undefined
,done
属性为true
。以后再调用next
方法,返回的都是这个值。
3、return方法
Generator.prototype.return()方法用于立即结束遍历,并返回给定的值。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return('foo')); // { value: "foo", done: true }
console.log(g.next()); // { value: undefined, done: true }
Generator函数的返回值是遍历器对象,但可以用return方法指定返回的值。参数就是返回值的value属性。使用return方法后,done属性将设为true,立即终结遍历Generator函数。
注意:
false的情况下,调用无参的return(),会将value设为undefined。
true的情况下,调用return(value)是没有意义的,参数也不会生效,value会固定为undefined。
4、Generator调用
next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
表达式后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
ES6 没有规定,function
关键字与函数名之间的星号,写在哪个位置。这导致下面的写法都能通过。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
由于 Generator 函数仍然是普通函数,所以一般的写法是上面的第三种,即星号紧跟在function
关键字后面。
5、yield表达式
next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
(1)next 方法运行逻辑
yield 表达式,则暂停执行后面的操作,并紧跟在 yield 后面的那个表达式的值,作为返回的对象的 value
next 方法时,再继续往下执行,直到遇到下一个 yield
yield 表达式,则一直运行到函数结束,直到 return 语句为止,并将 return 语句后面的表达式的值,作为返回的对象的 value
return 语句,则返回的对象的 value 属性值为 undefined。
(2)yield 与 return 对比
yield
表达式与return
语句既有相似之处,也有区别。
相似处:都能返回紧跟在语句后面的那个表达式的值。
不同处:
- 每次遇到
yield
,函数暂停执行,下一次再从该位置继续向后执行;return
语句不具备位置记忆的功能 - 一个函数里面,可以执行多次(或者说多个)
yield
表达式;只能执行一次(或者说一个)return
语句 - Generator 函数可以返回一系列的值,因为可以有任意多个
yield;
正常函数只能返回一个值,因为只能执行一次return
二、Generator函数异步应用
封装的异步任务,或者说是异步任务的容器。
yield
function* gen(x) {
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
遍历器)g。
1、Generator数据交换和错误处理
暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
(1)next实现函数体内外数据交换
next
返回值的 value 属性,是 Generator 函数向外输出数据;next
方法还可以接受参数,向 Generator 函数体内输入数据。
function* gen(x){
var y = yield x + 2;
return y;
}
var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }
第一个next
方法的value
属性,返回表达式x + 2
的值3
。
第二个next
方法带有参数2
,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量y
接收。因此,这一步的value
属性,返回的就是2
(变量y
的值)。
(2)函数内部错误处理
Generator 函数内部还可以部署错误处理代码,捕获函数体外抛出的错误。
function* gen(x){
try {
var y = yield x + 2;
} catch (e){
console.log(e);
}
return y;
}
var g = gen(1);
g.next();
g.throw('出错了');
throw
方法抛出的错误,可以被函数体内的try...catch
代码块捕获。
这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
2、异步任务封装
var fetch = require('node-fetch');
function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}
上面代码中,Generator 函数封装了一个异步操作,该操作先读取一个远程接口,然后从 JSON 格式的数据解析信息。
yield
命令。
执行这段代码:
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函数将异步操作表达很简洁,但流程管理不便。