Generator(生成器)函数除了状态机,还是一个遍历器对象生成函数
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next() // { value: 'hello', done: false }
hw.next() // { value: 'world', done: false }
hw.next() // { value: 'ending', done: true }
hw.next() // { value: undefined, done: true }
yield只在Generator函数内部调用 yield和return区别:
- yield:遇到yield,函数暂停执行,下一次再从该位置继续向后执行
- return:遇到return,函数直接返回,下面代码不执行
普通函数和Generator函数区别:
- 普通函数:调用直接执行
- Generator函数:调用next方法时才执行
function* f() {
console.log('执行了!')
}
console.log(1)
var generator = f();
console.log(2)
setTimeout(function () {
console.log(3)
generator.next()
}, 2000);
// 1
// 2
// 3
// 执行了!
对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true
- yield表达式本身没有返回值,或者说总是返回undefined
- next方法的参数表示上一个yield表达式的返回值(所以在第一次使用next方法时,传递参数是无效的)
// 案例一
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b
// 案例二
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next()
// 5 + 1
// Object{value:6, done:false}
a.next()
// y = (2 * undefined) = NaN
// z = NaN / 3 = NaN
// Object{value:NaN, done:false}
a.next()
// return 5 + NaN + undefined
// Object{value:NaN, done:true}
var b = foo(5);
b.next()
// 5 + 1
// { value:6, done:false }
b.next(12)
// y = 24
// z = y / 3 = 24 / 3 = 8
// { value:8, done:false }
b.next(13)
// return 5 + 24 + 13 = 42
// { value:42, done:true }
如果想要第一次就传参,可以封装函数,自动执行第一次
function wrapper(generatorFunction) {
return function (...args) {
let generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
}
const wrapped = wrapper(function* () {
console.log(`First input: ${yield}`);
return 'DONE';
});
wrapped().next('hello!')
// First input: hello!
for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法 done为true时结束遍历
// 注意:return 6 时,返回{value: 6, done: true},遍历不包含该对象
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
- throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
- throw方法
- 被捕获:Generator函数会附带执行下一条yield表达式
- 没有被内部捕获:Generator函数不会再执行下去
- throw方法出错误,Generator函数体外也可以捕获
var g = function* () {
try {
yield;
} catch (e) {
console.log('内部捕获', e);
}
yield console.log('执行')
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕获', e);
}
// 内部捕获 a
// 执行
// 外部捕获 b
// 没有被内部捕获:不会再执行下去
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke!');
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('第一次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第二次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
try {
v = generator.next();
console.log('第三次运行next方法', v);
} catch (err) {
console.log('捕捉错误', v);
}
console.log('caller done');
}
log(g());
// starting generator
// 第一次运行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉错误 { value: 1, done: false }
// 第三次运行next方法 { value: undefined, done: true }
// caller done
return方法,可以返回给定的值,并且终结遍历 Generator 函数。
- 如果 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会推迟到finally代码块执行完再执行。
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
g.next() // { value: undefined, done: true }
它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
在一个 Generator 函数里面执行另一个 Generator 函数。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
[bar()]
var obj = {
* myGeneratorMethod() {
···
}
};
var obj = {
myGeneratorMethod: function* () {
// ···
}
};
- 调用Generator函数返回的是遍历器对象,而不是this对象,所以Generator函数不能做构造函数
解决:
- 要取Generator函数的属性,可以挂在其原型对象上
- Generator函数返回的是遍历器对象,可以包装个外层函数
// 让Generator 函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this
function* gen() {
this.a = 1;
yield this.b = 2;
yield this.c = 3;
}
function F() {
return gen.call(gen.prototype);
}
var f = new F();
f.next(); // Object {value: 2, done: false}
f.next(); // Object {value: 3, done: false}
f.next(); // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3
clock函数就是一个状态机
Generator 实现与 ES5 实现对比:
- 少了用来保存状态的外部变量ticking
- 简洁,更安全(状态不会被非法篡改)
- 更符合函数式编程的思想
// ES5
var ticking = true;
var clock = function() {
if (ticking)
console.log('Tick!');
else
console.log('Tock!');
ticking = !ticking;
}
// Generator
var clock = function* () {
while (true) {
console.log('Tick!');
yield;
console.log('Tock!');
yield;
}
};
Generator 函数是 ES6 对协程的实现,但属于不完全实现。Generator 函数被称为“半协程”(semi-coroutine),意思是只有 Generator 函数的调用者,才能将程序的执行权还给 Generator 函数。如果是完全执行的协程,任何函数都可以让暂停的协程继续执行。
- Generator 函数一旦遇到yield命令,就会暂时退出堆栈,里面的所有变量和对象会冻结在当前状态。
- 执行next命令时,这个上下文环境又会重新加入调用栈,冻结的变量和对象恢复执行
function* gen() {
yield 1;
return 2;
}
let g = gen();
console.log(
g.next().value,
g.next().value,
);
- 执行g.next()时,Generator 函数gen的上下文会加入堆栈,即开始运行gen内部的代码
- 遇到yield 1时,gen上下文退出堆栈,内部状态冻结
- 第二次执行g.next()时,gen上下文重新加入堆栈,变成当前的上下文,重新恢复执行
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
// 当成功回调时,调用next继续
it.next(response);
});
}
var it = main();
it.next();
改善代码流程(同步)
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 方案一
function run(g){
var task = g.next()
console.log(task)
if (!task.done) {
run(g)
}
}
var g = bar()
run(g)
// {value: "x", done: false}
// {value: "a", done: false}
// {value: "b", done: false}
// {value: "y", done: false}
// {value: undefined, done: true}
// 方案二
for(let i of bar()){
console.log(i)
}
// x a b y
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
function* makeSimpleGenerator(array){
var nextIndex = 0;
while(nextIndex < array.length){
yield array[nextIndex++];
}
}
var gen = makeSimpleGenerator(['yo', 'ya']);
gen.next().value // 'yo'
gen.next().value // 'ya'
gen.next().done // true
function* doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
// task是一个函数,可以像回调函数那样使用它
}
// es5
function doStuff() {
return [
fs.readFile.bind(null, 'hello.txt'),
fs.readFile.bind(null, 'world.txt'),
fs.readFile.bind(null, 'and-such.txt')
];
}