Generator 函数是 es6 中的新的异步编程解决方案,本节仅讨论 Generator 函数自己,异步编程放在后面的部分。
Generator 函数以前也提到过,描述内部封装的多个状态,相似一个状态机,固然也是很好的 iterator 生成器。Generator 函数的基本形式以下:es6
function* gen(){ yield status1; yield status2; //... }
不难看出,Generator 函数在 function 关键字和函数名之间加了一个星号"*", 内部用 yield 返回每个状态。编程
固然还有其余格式的定义:数组
//函数表达式 var gen = function*(){ yield status1; //... }; //对象方法 var obj = { *gen(){ yield status1; //... } };
Generator 函数调用时,写法和普通函数同样。但函数并不执行执行时,返回内部自带 iterator,以后调用该 iterator 的 next() 方法, 函数会开始执行,函数每次执行遇到 yield 关键字返回对应状态,并跳出函数,当下一次再次调用 next() 的时候,函数会继续从上一次 yield 跳出的下一跳语句继续执行。固然 Generator 函数也能够用 return 返回状态,不过此时,函数就真的运行结束了,该遍历器就再也不工做了;若是函数内部因此的 yield 都执行完了,该遍历器同样再也不工做了:app
function* gen(){ yield "hello"; yield "world"; return "ending"; } var it = gen(); console.log(it.next()); //{value: "hello", done: false} console.log(it.next()); //{value: "world", done: false} console.log(it.next()); //{value: "ending", done: true} console.log(it.next()); //{value: undefined, done: true}
注意:异步
{value: undefined, done: true}
。 若是有 return 语句,该返回值对应的 value 属性值为 return 表达式的值。function* gen(){ console.log("hello" + (yield)); //yield 用做表达式参数必须加() let input = yield; foo(yield 'a', yield 'b'); }
[Symbol.iterator]
是函数本身:function* gen(){} var g = gen() g[Symbol.iterator]() === g; //true
yield 语句自己具备返回值,返回值是下一次调用 next 方法是传入的值。next 方法接受一个参数,默认 undefined:异步编程
function* f(){ for(let i = 0; true; i++){ var reset = yield i; if(reset) i = -1; } } var g = f(); console.log(g.next().value) //0 console.log(g.next().value) //1 console.log(g.next().value) //2 console.log(g.next(true).value) //0
上面 代码第3行var reset = yield i
等号右侧是利用 yield 返回i, 因为赋值运算时右结合的,返回 i 之后,函数暂停执行,赋值工做没有完成。以后再次调用 next 方法时,将此次传入参数做为刚才这个 yield 的返回值赋给了 reset, 所以计数器被重置。函数
function* foo(x){ var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var g = foo(5); console.log(g.next()); //{value: 6, done: false} console.log(g.next(12)); //{value: 8, done: false} console.log(g.next(13)); //{value: 42, done: true}
第一次调用 next 函数不须要参数,做为 Generator 启动,若是带了参数也会被忽略。固然,若是必定想在第一次调用 next 时候就赋值,能够将 Generator 函数封装一下:post
//一种不完善的思路,一般不强求这样作 function wrapper(gen){ return function(){ let genObj = gen(...arguments); genObj.next(); //提早先启动一次,但若是此时带有返回值,该值就丢了! return genObj; } } var gen = wrapper(function*(){ console.log(`first input: "${yield}"`); }); var it = gen(); it.next("Bye-Bye"); //first input: "Bye-Bye"
咱们注意到,以前在 iterator 中,迭代器最后返回{value: undefined, done: true}
,其中值为 undefined 和 done 为 true 是同时出现的,而遍历结果不包含 done 为 true 时对应的 value 值,因此 Generator 的 for...of 循环最好不要用 return 返回值,由于该值将不会被遍历:优化
function* gen(){ for(var i = 0; i < 5; i++){ yield i; } return 5; } for(let v of gen()){ console.log(v); //依次输出 0, 1, 2, 3, 4, 没有 5 }
除了 for...of, Generator 还有不少简单用法。下面利用 fibonacci 数列,演示几种不一样的 Generator 用法:this
function* fib(n = Infinity){ var a = 1, b = 1; while(n){ yield a; [a, b] = [b, a + b]; n--; } } console.log([...fib(10)]); //1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
function* fib(n = Infinity){ var a = 1, b = 1; while(n){ yield a; [a, b] = [b, a + b]; n--; } } var [a, b, c, d, e, f] = fib(); //a=1, b=1, c=2, d=3, e=5, f=8
function* fib(n = Infinity){ var a = 1, b = 1; while(n){ yield a; [a, b] = [b, a + b]; n--; } } var set = new Set(fib(n)); console.log(set); //Set(9) [1, 2, 3, 5, 8, 13, 21, 34, 55]
function* fib(n = Infinity){ var a = 1, b = 1; var n = 10; while(n){ yield a; [a, b] = [b, a + b]; n--; } } var arr = Array.from(fib(10)); console.log(arr); //[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
function* entries(obj){ for(let key of Object.keys(obj)){ yield [key, obj[key]]; } } var obj = { red: "#ff0000", green: "#00ff00", blue: "#0000ff" }; for(let [key, value] of entries(obj)){ console.log(`${key}: ${value}`); //依次输出 "red: #ff0000", "green: #00ff00", "blue: #0000ff" }
Generator 返回的遍历器对象具throw() 方法, 通常的遍历器用不到这个方法。该方法接受一个参数做为抛出的错误,该错误能够在 Generator 内部捕获:
function* gen(){ while(1){ try{ yield "OK"; } catch(e) { if(e === 'a') console.log(`内部捕获: ${e}`); //内部捕获: a else throw e; } } } var it = gen(); it.next(); //若是没有这一行启动生成器,结果仅输出:外部捕获: a try{ it.throw('a'); it.throw('b'); it.next(); //上一行错误为外部捕获,try 中的代码不在继续执行,故这一行不执行 } catch(e) { console.log(`外部捕获: ${e}`) //外部捕获: b }
throw参数在传递过程当中和 next 参数相似,须要先调用一次 next 方法启动生成器,以后抛出的错误会在前一个 yield 的位置被捕获:
function* gen(){ yield "OK"; //错误被抛到这里,不在内部 try 语句内没法捕获 while(1){ try{ yield "OK"; } catch(e) { console.log(`内部捕获: ${e}`); } } } var it = gen(); it.next(); try{ it.throw('a'); } catch(e) { console.log(`外部捕获: ${e}`) //外部捕获: a }
注意: 不要混用 throw() 方法和 throw 语句,后者没法将错误抛到生成器内部。其次,throw 会终止遍历器,不能继续工做,而 throw 不会终止遍历器:
function* gen(){ yield console.log("hello"); yield console.log("world"); } //throw 语句 var it1 = gen(); it1.next(); //hello try{ throw new Error(); } catch(e) { it1.next() //world } //throw() 方法 var it2 = gen(); it2.next(); //hello try{ it2.throw(); } catch(e) { it2.next() //遍历器被关闭没法执行, 静默失败 }
若是在遍历器内部抛出错误,遍历器停止,继续调用 next() 方法将获得{value: undefined, done: true}
:
function* gen(){ var x = yield "ok"; var y = yield x.toUpperCase(); var z = yield (x + y + z); } //throw 语句 var it = gen(); it.next(); //"ok" try{ it.next(); } catch(e) { console.log("Error Caught"); //Error Caught } finally { it.next(); //{value: undefined, done: true} }
return() 方法返回指定的值,并终止迭代器:
var it = (function* gen(){ yield 1; yield 2; yield 3; }()); console.log(it.next()); //{value: 1, done: false} console.log(it.next()); //{value: 2, done: false} console.log(it.return("end")); //{value: "end", done: true} console.log(it.next()); //{value: undefined, done: true}
若是不给 return() 方法提供参数,默认是 undefined
若是 Generator 中有 try...finally 语句,return 会在 finally 执行完再执行:
function* numbers(){ yield 1; try{ yield 2; yield 3; } finally { yield 4; yield 5; } yield 6; } var g = numbers(); console.log(g.next().value); //1 console.log(g.next().value); //2 console.log(g.return("end").value); //延迟到 finally 以后输出 ----- console.log(g.next().value); //4 | console.log(g.next().value); //5 | //"end" <------------------- console.log(g.next().value); //undefined
在一个 Generator 中调用另外一个 Generator 函数默认是没有效果的:
function* gen(){ yield 3; yield 2; } function* fun(){ yield gen(); yield 1; } var it = fun(); console.log(it.next().value); //gen 函数返回的遍历器 console.log(it.next().value); //1 console.log(it.next().value); //undefined
显然第一次返回的结果不是咱们想要的。须要使用 yield 解决这个问题。yield 将一个可遍历结构解构,并逐一返回其中的数据。
function* gen(){ yield 3; yield 2; } function* fun(){ yield* gen(); yield 1; } var it = fun(); console.log(it.next().value); //3 console.log(it.next().value); //2 console.log(it.next().value); //1
function* fun(){ yield* [4,3,2]; yield 1; } var it = fun(); console.log(it.next().value); //4 console.log(it.next().value); //3 console.log(it.next().value); //2 console.log(it.next().value); //1
被代理的 Generator 能够用return向代理它的 Generator 返回值:
function* gen(){ yield "Bye"; yield* "Hi" return 2; } function* fun(){ if((yield* gen()) === 2) yield* "ok"; else yield "ok"; } var it = fun(); console.log(it.next().value); //Bye console.log(it.next().value); //H console.log(it.next().value); //i console.log(it.next().value); //o console.log(it.next().value); //k console.log(it.next().value); //undefined
举例:
//方法1: var arr = [1,2,[2,[3,4],2],[3,4,[3,[6]]]]; function plat(arr){ var temp = []; for(let v of arr){ if(Array.isArray(v)){ plat(v); } else { temp.push(v); } } return temp; } console.log(plat(arr)); //[1, 2, 2, 3, 4, 2, 3, 4, 3, 6] //方法2: function* plat2(arr){ for(let v of arr){ if(Array.isArray(v)){ yield* plat2(v); } else { yield v; } } } var temp = []; for(let x of plat2(arr)){ temp.push(x); } console.log(temp); //[1, 2, 2, 3, 4, 2, 3, 4, 3, 6]
//节点 function Node(value, left, right){ this.value = value; this.left = left; this.right = right; } //二叉树 function Tree(arr){ if(arr.length === 1){ return new Node(arr[0], null, null); } else { return new Node(arr[1], Tree(arr[0]), Tree(arr[2])); } } var tree = Tree([[[1], 4, [5]], 2, [[[0], 6, [9]], 8, [7]]]); //前序遍历 function* preorder(tree){ if(tree){ yield tree.value; yield* preorder(tree.left); yield* preorder(tree.right); } } //中序遍历 function* inorder(tree){ if(tree){ yield* inorder(tree.left); yield tree.value; yield* inorder(tree.right); } } //后序遍历 function* postorder(tree){ if(tree){ yield* postorder(tree.left); yield* postorder(tree.right); yield tree.value; } } var _pre = [], _in = [], _post = []; for(let v of preorder(tree)){ _pre.push(v); } for(let v of inorder(tree)){ _in.push(v); } for(let v of postorder(tree)){ _post.push(v); } console.log(_pre); //[2, 4, 1, 5, 8, 6, 0, 9, 7] console.log(_in); //[1, 4, 5, 2, 0, 6, 9, 8, 7] console.log(_post); //[1, 5, 4, 0, 9, 6, 7, 8, 2]
//传统实现方法 var clock1 = function(){ var ticking = false; return { next: function(){ ticking = !ticking; if(ticking){ return "Tick"; }else{ return "Tock"; } } } }; var ck1 = clock1(); console.log(ck1.next()); //Tick console.log(ck1.next()); //Tock console.log(ck1.next()); //Tick //Generator 方法 var clock2 = function*(){ while(1){ yield "Tick"; yield "Tock"; } }; var ck2 = clock2(); console.log(ck2.next().value); //Tick console.log(ck2.next().value); //Tock console.log(ck2.next().value); //Tick
在ES6中, 规定了全部 iterator 是 Generator 函数的实例:
function* gen(){} var it = gen(); it instanceof gen; //true console.log(gen.__proto__); //GeneratorFunction console.log(gen.__proto__.__proto__); //Function console.log(gen.constructor); //GeneratorFunction console.log(gen.__proto__.constructor); //GeneratorFunction gen.prototype.sayHello = function(){ console.log("hello"); } it.sayHello(); //"hello"
可是 Generator 函数中的 this 并不指向生成的 iterator:
function* gen(){ this.num = 11; console.log(this); } var it = gen(); console.log(it.num); //undefined it.next(); //Window var obj = { * fun(){ console.log(this); } } var o_it = obj.fun(); o_it.next(); //obj
由上面这个例子不难看出,Generator 函数中的 this 和普通函数是同样的。不过,可不能够把 Generator 函数做为构造函数呢?显然是不行的:
function* gen(){ this.num = 11; } gen.prototype.say = function(){console.log("hello")} var a = new gen(); //TypeError: gen is not a constructor
ES7 在数组推导的基础上提出了 Generator 函数推导,惋惜这个功能目前还不能使用:
let gen = function*(){ for(let i = 0; i < 6; i++){ yield i; } }; let arr = [for(let n of gen()) n * n]; //至关于: let arr = Array.from(gen()).map(n => n * n); console.log(arr); [0,1,4,9,16,25]
Generator 数组推导,利用惰性求值优化系统资源利用:
var bigArr = new Array(10000); for(let i = 0; i < 10000; i++){ bigArr.push(i); } //....其余代码 //使用 bigArr 以前好久就分配了内存 console.log(bigArr[100]); var gen = function*(){ for(let i = 0; i < 10000; i++){ yield i; } }; //....其余代码 //使用 bigArr 时才分配内存 var bigArr = [for(let n of gen()) n]; console.log(bigArr[100]);
//伪代码 function* main(){ var result = yield request("http://url.com"); var res = JSON.parse(result); console.log(res.value); } function request(url){ var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onreadystatechange = function(){ if(xhr.readyState == 4 && xhr.status == 200){ it.next(xhr.response); } } xhr.send(); } var it = main(); it.next();
另外一个例子:
//伪代码 //遇到多重回调函数,传统写法: step1(function(value1){ step2(value1, function(value2){ step3(value2, function(value3){ step4(value3, function(value4){ //do something }); }); }); }); //利用 Generator 写: function* gen(){ try{ var value1 = yield step1(); var value2 = yield step2(value1); var value3 = yield step3(value2); var value4 = yield step4(value3); } catch(e) { //Handle the error form step1 to step4 } }