ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案
常用于异步编程的还包括 promise 对象的应用,与 async/await 指令
其中 * 用来表示函数为 Generator 函数,yield 用来定义函数内部的状态。
function* func() {
console.log("one");
console.log("more");
yield "1"; //指针指到yield的位置,程序就会被挂起
console.log("two");
yield "2";
console.log("three");
return "3";
}
调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可,但是 Generator 函数不会像普通函数一样立即执行,而是返回一个指向内部状态对象的指针
,所以要调用遍历器对象 Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。
let f = func();
const s = f.next(); // one
console.log(s); // {value: "1", done: false}
console.log("-----------");
f.next(); // two
// {value: "2", done: false}
f.next(); // three
// {value: "3", done: true}
f.next();
// {value: undefined, done: true}
调用 next 方法时,从 Generator 函数的头部开始执行,执行到 yield 就停下来,并将 yield 后边表达式的值 ‘1’,作为调用 next 方法返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false,若是函数被执行完成则设置 done 属性值是 true
每次调用 next 方法时,同上步,在下一个 yield 处挂起
直到 next 方法返回对象的 done 属性首次为 true,表示函数执行完成,此时根据函数是否有返回值 return,如果有则 value 为 return 值,若没有则 value 为 undefined
如果函数已经执行完成了,仍旧调用 next 方法,则返回 value 属性值是 undefined ,done 属性值是 true
一般情况下,next 方法不传入参数的时候,yield 表达式的返回值是 undefined 。当 next 传入参数的时候,该参数会作为上一步 yield 的返回值。
function* sendParameter() {
console.log("start");
var x = yield "2";
console.log("one:" + x);
var y = yield "3";
console.log("two:" + y);
console.log("total:" + (x + y));
}
//next 不传参
var send1 = sendParameter();
send1.next(); // start
// {value: "2", done: false}
send1.next(); // one:undefined
// {value: "3", done: false}
send1.next();
// two:undefined
// total:NaN
// {value: undefined, done: true}
//next 传参
var send2 = sendParameter();
send2.next(10); // start
// {value: "2", done: false}
send2.next(20); // one:20
// {value: "3", done: false}
send2.next(30);
// two:30
// total:50
// {value: undefined, done: true}
{value: , done: }
let x=yield * n
,参数会作为上一步通过 yield 赋值的变量 x 的值
除了使用 next ,还可以使用 for... of 循环遍历 Generator 函数生产的 Iterator 对象
。
return 方法返回给定值,并直接结束遍历 Generator 函数。
return 方法提供参数时,返回该参数;不提供参数时,返回 undefined(与 Generator 函数是否有 return 返回值无关)
function* foo() {
yield 1;
yield 2;
yield 3;
}
var f = foo();
f.next(); // {value: 1, done: false}
f.return("foo"); // {value: "foo", done: true}
f.next(); // {value: undefined, done: true}
function* bamboo() {
yield 1;
yield 2;
return "3";
}
bamboo.return(); // {value: undefined, done: true}
throw 方法可以在 Generator 函数体外面抛出异常,在函数体内部捕获,而捕获 throw 异常与指针所在 try-catch 息息相关
var g = function* () {
try {
yield;
} catch (e) {
console.log("catch inner", e);
}
};
var i = g();
i.next();
try {
i.throw("a"); // catch inner a
i.throw("b");
i.throw("c");
} catch (e) {
console.log("catch outside", e); // catch outside b
}
遍历器对象调用 throw()方法:
当注释掉 next 之后发现,只有外部的 catch 可以捕获异常
通过以上示例发现,当注释掉 next 方法的时候 throw 异常并没有执行 Generator 函数内部的 try-catch!!!
经过分析认为,当执行 next()方法后,指针指向了 Generator 函数体内
var g = function* () {
try {
yield;
} catch (e) {
console.log("catch inner", e);
}
};
/* ------- 测试-throw("c")可调用位置 --------*/
var i = g();
i.next();
try {
try {
i.throw("a"); // catch inner a
i.throw("b"); //catch outside b
} catch (e) {
console.log("catch outside", e);
i.throw("c"); //catch outside second c
}
} catch (e) {
console.log("catch outside second", e);
}
/* ------- 测试 -2. 不调用next()方法 --------*/
var i = g();
// i.next();
try {
i.throw("a");
i.throw("b");
i.throw("c");
} catch (e) {
console.log("catch outside", e); // catch outside a
}
遍历器对象调用 next()方法区别:
throw(“c”)不想被跳过,不能再放置在 try 代码块,因为 try 代码块中触发异常位置之后的代码都不会在执行,将它放在不会跳过的位置,则会被调用
/* ------- 测试 -3. 不使用yield --------*/
var g = function* () {
try {
// yield;
const x = 1;
// yield;
} catch (e) {
console.log("catch inner", e);
}
};
var i = g();
i.next();
try {
i.throw("a"); //catch outside a
i.throw("b");
i.throw("c");
} catch (e) {
console.log("catch outside", e);
}
/* - 测试 -4. 同一 try 分支中多次使用 yield ----*/
var g = function* () {
try {
yield;
const x = 1;
yield;
} catch (e) {
console.log("catch inner", e);
}
};
var i = g();
i.next();
try {
i.throw("a"); //catch inner a
i.throw("b"); //catch outside b
i.throw("c");
} catch (e) {
console.log("catch outside", e);
}
/* - 测试 -5. 不同 try 分支多次使用 yield ----*/
var g = function* () {
try {
try {
yield;
const x = 1;
} catch (e) {
console.log("catch inner", e);
}
yield;
const y = 1;
} catch (e) {
console.log("catch inner second", e);
}
};
//等价于
var g = function* () {
try {
yield;
const x = 1;
} catch (e) {
console.log("catch inner", e);
}
try {
yield;
const y = 1;
} catch (e) {
console.log("catch inner second", e);
}
};
var i = g();
i.next();
try {
i.throw("a"); //catch inner a
i.throw("b"); //catch inner second b
i.throw("c"); //catch outside c
} catch (e) {
console.log("catch outside", e);
}
遍历器对象使用 yield 的区别:
遍历器对象调用 throw 方法,就会调用指针所在的 try-catch ,如果 Generator 函数内部不存在 try-catch,就开始调用遍历器对象调用 throw 方法所在的函数体外 try-catch
指针与 Generator 函数返回遍历器对象的 next 方法和 Generator 函数体内 yield 相关
指针指向遍历器对象,如果 Generator 函数中存在 yield,调用 next 方法才会指向函数内部,否则仍然是指向遍历器对象
调用遍历器对象的 throw 方法,指针会按顺序指向Generator函数体内 的 yield,所以触发的是 yield 所在的 try-catch,而没有 yield 的 try-catch 自然也不会触发!!!!而当指针指向遍历器对象时,触发的就是调用 throw 方法时所在的 try-catch!!!!
yield* 表达式表示 yield 返回一个遍历器对象,用于在 Generator 函数内部,调用另一个 Generator 函数。
function* callee() {
console.log("callee: " + (yield));
}
function* caller() {
while (true) {
yield* callee();
}
}
const callerObj = caller();
callerObj.next();
//yield无值因此不执行console.log("callee: " + (yield))
//并且callee()函数没有返回值,所以value=undefined
// {value: undefined, done: false}
callerObj.next("a"); // callee: a
// {value: undefined, done: false}
callerObj.next("b"); // callee: b
// {value: undefined, done: false}
// 等同于
// function* caller() {
// while (true) {
// //TypeError: callee is not iterable
// for (var value of callee) {
// yield value;
// }
// }
// }
实现 Iterator,为不具备 Iterator 接口的对象提供遍历方法。
function* objectEntries(obj) {
const propKeys = Reflect.ownKeys(obj);
for (const propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
const jane = { first: "Jane", last: "Doe" };
for (const [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
Reflect.ownKeys() 返回对象所有的属性数组,不管属性是否可枚举,包括 Symbol。
jane 对象原生是不具备 Iterator 接口无法通过 for… of 遍历。用 Generator 函数加上了 Iterator 接口,所以就可以遍历 jane 对象了。
对于遍历对象的属性非常方便