Generator 是一个分步执行的函数,可以根据需要执行下一步。合理利用这个特性可以很好解决异步编程回调嵌套的问题。
Generator很像是一个函数,但是你可以暂停它的执行。你可以向它请求一个值,于是它为你提供了一个值,但是余下的函数不会自动向下执行直到你再次向它请求一个值。
function
后面跟 *
,函数体内有 yield
关键字yield
暂停执行,调用 next
继续执行next
方法返回的对象中 value
表示当前指针对应的yield语句的返回值,done
表示遍历是否结束{value: undefined, done: true}
return
,则返回 return
后面表达式的值做为 value
的值,如果没有,则返回undefined
做为 value
的值
function* fn1(){
yield
'a'
yield
'b'
return
'c';
}
var g1 = fn1();
g1.next();
//{value: "a", done: false}
g1.next();
//{value: "b", done: false}
g1.next();
//{value: "c", done: false}
g1.next();
//{value: undefined, done: true}
|
多维数组扁平化
var arr = [
1, [[
2,
3],
4], [
5,
6]];
var flat =
function* (a){
var len = a.length, item;
for(
var i=
0; i
item = a[i];
if(
typeof item !==
'number'){
yield* flat(item);
}
else{
yield item;
}
}
}
var g2 = flat(arr);
g2.next()
//{value: 1, done: false}
for(
var f
of g2){
console.log(f)
}
// 2,3,4,5,6
|
任意一个对象的 Symbol.iterator
方法,等于该对象的遍历器生成函数
将 Generator
函数赋值给对象的 Symbol.iterator
属性,可使对象具有Iterator接口
具有Iterator接口的对象可使用 ...
延展符展开
var k = {};
k[
Symbol.iterator] =
function* (){
yield
1;
yield
2;
}
[...k]
//[1, 2]
|
Generator
函数返回的遍历器对象也有Symbol.iterator
对象,执行后返回自身
var m =
function* (){}
var g3 = m();
g3[
Symbol.iterator]() == g3
//true
|
yield
语句本身没有返回值,或者永远返回 undefined
next
方法有参数时,参数将会被当然上一个 yield
语句的返回值
function* f() {
for(
var i=
0;
true; i++) {
//这里,如果next没有传参数过来,reset将一直是undefined
var reset =
yield i;
console.log(
typeof reset)
if(reset) { i =
-1; }
}
}
var g = f();
g.next();
//{value: 0, done: false}
g.next();
//undefined {value: 1, done: false}
g.next(
true);
//boolean {value: 0, done: false}
|
Generator函数在运行时,内部的 上下文(context )是保持不变的,但可以通过 next
方法不但的向函数体注入参数,从而改变函数的行为。next
方法第一次调用时的参数会被忽略,只有后面的传参会被使用,第一个next方法可视作是启动遍历器对象
function* foo(x) {
var y =
2 * (
yield (x +
1));
var z =
yield (y /
3);
return (x + y + z);
}
//这里,由于yield本身的返回值是undefined,所以导致后面的计算都为NaN
var a = foo(
5);
a.next()
// Object{value:6, done:false}
a.next()
// Object{value:NaN, done:false}
a.next()
// Object{value:NaN, done:true}
//这里,第二次的next传递了参数12,y=24, z=24/3=>8
//第三次 x=5 z=13 y=24 故x+y+z = 42
var b = foo(
5);
b.next()
// { value:6, done:false }
b.next(
12)
// { value:8, done:false }
b.next(
13)
// { value:42, done:true }
|
for...of
遍历 可以使用 for...of
遍历Generator
对象,不需要调用 next
function* fn(){
yield
1;
yield
2;
yield
3;
return
4;
}
for(
var i
of fn()){
console.log(i)
}
//1,2,3
|
由于 for...of
遍历时,当返回的对象 done
为 true
时就终止了循环,且不返回对象的 value
, 所以最后的 4 没有输出,执行到 return
返回的是 {value: 4, done: true}
例1:利用Generator函数和for…of循环,实现斐波那契数列
function* fibonacci(){
let [prev, curr] = [
0,
1];
for(;;){
[prev, curr] = [curr, prev+curr];
yield curr;
}
}
for(
var y
of fibonacci()){
if(y>
50)
break;
console.log(y);
}
// 1, 2, 3, 5, 8, 13, 21, 24
|
例2:给原生对象添加Iterator遍历接口
function* objectEntries(){
let keys =
Object.keys(
this);
for(
let key
of keys){
yield [key,
this[key]]
}
}
var obj = {
name:
'jack',
age:
12,
city:
'hangzhou'}
obj[
Symbol.iterator] = objectEntries;
for(
let [key, value]
of obj){
console.log(
`${key}: ${value}`)
}
//name: jack
//age: 12
//city: hangzhou
|
for...of
循环 / 扩展运算符...
/ 解构赋值 / Array.from
都可以操作Generator函数返回的Iterator对象
function* numbers(){
yield
1;
yield
2;
return
3;
yield
4;
}
[...numbers()]
//[1, 2]
Array.from(numbers())
//[1, 2]
let [x, y] = numbers()
//[1, 2]
for(
let n
of numbers()){
console.log(n)
}
//1, 2
|
Generator函数内部部署 try...catch
可以对多个 yield 语句进行错误捕捉
Generator函数内部抛出错误可以被函数体外catch捕获
Generator函数体外抛出错误可以被函数体内catch捕获
function* fnA(){
let x =
1;
try{
yield
console.log(
'a')
yield
console.log(
'b')
//这里报错会中断后面的执行,返回{value: undefined, done: true}
yield x.toUppercase()
yield
console.log(
'd')
}
catch(e){
console.log(
'内:', e)
throw e
}
}
var ga = fnA()
try{
ga.next();
//外面的错误会被内部catch捕获
ga.throw(
'在函数体外部抛出的错误')
}
catch(e){
console.log(
'外:', e)
}
//由于内部中断了执行,再调用next只会拿到{value: undefined, done: true}的结果
ga.next()
//输出结果:
//a
//内: 在函数体外部抛出的错误
//外: 在函数体外部抛出的错误
//{value: undefined, done: true}
|
如果Generator函数返回的Iterator对象在外部抛出了错误,而函数体内部又没有捕获,那么这个错误将抛给全局,程序中断
var gen =
function* gen(){
yield
console.log(
'hello');
yield
console.log(
'world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined 报错
|
Generator函数返回的Iterator对象throw一个错误后,会同时调用一次next方法
var gen =
function* gen(){
try {
yield
console.log(
'a');
}
catch (e) {
// ...
}
yield
console.log(
'b');
yield
console.log(
'c');
}
var g = gen();
g.next()
// a
g.throw()
// b
g.next()
// c
|
遍历器对象调用return可以改变返回的结果的vaule,并结束遍历返回的done为true
如果Generator函数内部有try…finally代码块,那么return方法会推迟到finally代码块执行完再执行
function* numbers () {
yield
1;
try {
yield
2;
yield
3;
}
finally {
yield
4;
yield
5;
}
yield
6;
}
var g = numbers()
g.next()
// { done: false, value: 1 }
g.next()
// { done: false, value: 2 }
g.return(
7)
// { done: false, value: 4 }
g.next()
// { done: false, value: 5 }
g.next()
// { done: true, value: 7 }
|
用于在一个Generator函数里面执行另一个Generator函数
function* fn1(){
yield
4;
yield
5;
}
function* fn2(){
yield
1;
yield
2;
yield
3;
yield* fn1();
yield
6;
}
function* fn3(){
yield
1;
yield
2;
yield fn1();
yield
6;
}
[...fn2()]
//[1, 2, 3, 4, 5, 6] 将fn1中的内容展开了
[...fn3()]
//[1, 2, fn1, 6] fn1为Generator返回的iterator对象
|
yield* 在Generator函数中可以遍历任何具有Iterator接口的对象
function* fn4(){
yield* [
1,
3,
4]
yield*
'Hllo'
}
[...fn4()]
//[1, 3, 4, "H", "l", "l", "o"]
|
yield 后面的Generator函数中可以通过 return 返回数据给 yield 所在的Generator函数
function* fn5(){
yield
1;
yield
2;
return
3;
}
function* fn6(){
yield
4;
var res =
yield* fn5();
yield res;
yield
5;
}
[...fn6()]
//[4, 1, 2, 3, 5]
|
多维数组转一维数组
function* flatArr(arr){
if(
Array.isArray(arr)){
for(
var k =
0; k
yield* flatArr(arr[k])
}
}
else{
yield arr;
}
}
var arr1 = [
1,[
2,
3,[
4,
5,
6]],
7,[
8,
9]]
for(
var i
of flatArr(arr1)){
console.log(i)
}
// 1,2,3,4,5,6,7,8,9
|
二叉树遍历
function Tree(left, label, right){
this.left = left;
this.label = label;
this.right = right;
}
function make(arr){
if(arr.length ===
1)
return
new Tree(
null, arr[
0],
null);
return
new Tree(make(arr[
0]), arr[
1], make(arr[
2]))
}
function* inorder(t){
if(t){
yield* inorder(t.left)
yield t.label
yield* inorder(t.right)
}
}
var tree = make([[[
'a'],
'b', [
'c']],
'd', [[
'e'],
'f', [
'g']]]);
var res = [];
for(
var k
in inorder(tree)){
res.push(k)
}
console.log(res)
//["a", "b", "c", "d", "e", "f", "g"]
|
做为对象属性
var obj = {
*fn(){
yield
1;
yield
2;
}
}
//等同于
var obj2 = {
fn:
function* (){
yield
1;
yield
2;
}
}
[...obj.fn()]
//[1, 2]
[...obj2.fn()]
//[1, 2]
|
状态保持
function* tick(){
while(
true){
yield
'Tick'
yield
'Tock'
}
}
var t = tick();
t.next();
t.next();
t.next();
t.next();
|
异步操作同步化
使用嵌套回调
function asyncFn1(done){
setTimeout(
()=>{
console.log(
1);
done(
1);
},
1500);
}
function asyncFn2(done){
setTimeout(
()=>{
console.log(
2);
done(
2);
},
2000);
}
function asyncFn3(done){
setTimeout(
()=>{
console.log(
3);
done();
},
2500);
}
asyncFn1(
function(args1){
asyncFn2(
function(args2){
asyncFn3(
function(args3){
//...
})
})
})
|
使用Generator封装,可以简化回调,让异步写起来更像是同步
//使用done回调保证任务按顺序执行
function asyncFn1(done){
setTimeout(
()=>done(
1),
1500);
}
function asyncFn2(done){
setTimeout(
()=>done(
2),
2000);
}
function asyncFn3(done){
setTimeout(
()=>done(
3),
2500);
}
//co的简单实现
function co(task){
var gen = task();
next();
function next(res){
var ret;
ret = gen.next(res);
if(ret.done)
return;
if(
typeof ret.value ===
'function'){
ret.value(
function(){
//依次遍历执行,如果没遍历完成,则递归调用
next.apply(
this,
arguments)
})
return;
}
}
}
co(
function* task(){
try{
var res1 =
yield asyncFn1;
//第1次 next fn1返回结果1
console.log(res1)
//第2次 next 传递fn1的结果 1
var res2 =
yield asyncFn2;
console.log(res2)
//第3次 next 传递fn2的结果 2
var res3 =
yield asyncFn3;
console.log(res3)
//第4次 next 传递fn3的结果 3
}
catch(e){
// ...
}
})
// 1, 2, 3
|
依次执行数组中的步骤
var step1 =
() =>
1;
var step2 =
() =>
2;
var step3 =
() =>
3;
var steps = [ step1, step2, step3 ]
function* iterateSteps(steps){
for(
var step
of steps){
yield step()
}
}
[...iterateSteps(steps)]
// [1, 2, 4]
|
将多个步骤组合成多个任务,并依次执行这多个任务
var step1 =
() =>
1;
var step2 =
() =>
2;
var step3 =
() =>
3;
var step4 =
() =>
4;
var step5 =
() =>
5;
var step6 =
() =>
6;
var step7 =
() =>
7;
var step8 =
() =>
8;
var job1 = [ step1, step2, step3 ]
var job2 = [ step4, step5, step6 ]
var job3 = [ step7, step8 ]
var jobs = [job1, job2, job3]
function* iterateSteps(steps){
for(
var step
of steps){
yield step()
}
}
function* iterateJobs(jobs){
for(
var job
of jobs){
yield* iterateSteps(job)
}
}
[...iterateJobs(jobs)]
//[1, 2, 3, 4, 5, 6, 7, 8]
|
给任意对象部署Iterator接口
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);
}
|
function doStuff(){
return [
1,
2,
3]
}
for(
var i
of doStuff()){
console.log(i)
}
// 1, 2, 3
function* genStuff(){
yield
1;
yield
2;
yield
3;
}
for(
var i
of genStuff()){
console.log(i)
}
// 1, 2, 3
|