一、generator是什么
简单理解,generator是一个返回多个值的函数
function* outNumber(){
yield 1;
yield 2;
yield 3;
}
const a = outNumber();
a.next(); //{value: 1, done: false}
a.next(); //{value: 2, done: false}
a.next(); //{value: 3, done: false}
a.next(); //{value: undefined, done: true}
//可以使用return,当到return的时候变为done
function* outNumber(){
yield 1;
yield 2;
return 3;
}
const a = outNumber();
a.next(); //{value: 1, done: false}
a.next(); //{value: 2, done: false}
a.next(); //{value: 3, done: true}
a.next(); //{value: undefined, done: true}
二、Generator.prototype.next
当next传入参数,他会去重置上一次yield的值
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
//每次运行到yield表达式,变量reset的值总是undefined。当next方法带一个参数true时,
变量reset就被重置为这个参数(即true),因此i会等于-1,下一轮循环就会从-1开始递增。
让我们来看另外一个例子
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false} yield 5 + 1,所以是6,但是y = 2 * undefined = NaN
a.next() // Object{value:NaN, done:false} yield NaN/3,所以是NaN,但是z = undefined
a.next() // Object{value:NaN, done:true} return(5 + NaN + undefined) 所以是NaN
var b = foo(5);
b.next() // { value:6, done:false } yield 5 + 1,所以是6,但是y = 2 * undefined = NaN
b.next(12) // { value:8, done:false } 传入参数,去重置第一次的yield(x + 1) = 12, 所以 var y = 2 * 12 = 24, 然后 yield(24/3) = 8
b.next(13) // { value:42, done:true } 传入参数,去重置第二次的yield(y / 3) = 13, 所以 z = 13, 但并没有去重置上一次中第一次yield的值,所以y依旧等于24,所以是 5 + 24 + 13
第一次 6
第二次 y = 2 * 12 = 24 z = 24 / 3 = 8
第三次 z = 13 , 上一次的 y = 24 , x= 5
三、for...of循环
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
//这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象
,所以上面代码的return语句返回的6,不包括在for...of循环之中
除了for...of
循环以外,扩展运算符(...
)、解构赋值和Array.from
方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 扩展运算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解构赋值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循环
for (let n of numbers()) {
console.log(n)
}
四、Generator.prototype.return
Generator 函数返回的遍历器对象,还有一个return
方法,可以返回给定的值,并且终结遍历 Generator 函数。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
上面代码中,遍历器对象g
调用return
方法后,返回值的value
属性就是return
方法的参数foo
。并且,Generator 函数的遍历就终止了,返回值的done
属性为true
,以后再调用next
方法,done
属性总是返回true
。
如果return
方法调用时,不提供参数,则返回值的value
属性为undefined
。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return() // { value: undefined, done: true }
如果 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 }
上面代码中,调用return()
方法后,就开始执行finally
代码块,不执行try
里面剩下的代码了,然后等到finally
代码块执行完,再返回return()
方法指定的返回值。
五、Generator.prototype.next | return | throw
next()
、throw()
、return()
这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield
表达式。
next()
是将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;
throw()
是将yield
表达式替换成一个throw
语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
return()
是将yield
表达式替换成一个return
语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
六、yield* 表达式
如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,自己手动完成遍历。
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
// 手动遍历 foo()
for (let i of foo()) {
console.log(i);
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// x
// a
// b
// y
上面代码中,foo
和bar
都是 Generator 函数,在bar
里面调用foo
,就需要手动遍历foo
。如果有多个 Generator 函数嵌套,写起来就非常麻烦。
ES6 提供了yield*
表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。
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"
看视频看视频,hold不住
七、Generator在异步中的应用
我们来看一个具体的业务场景
function getData(x) {
return new Promise(function (resolve, reject) {
setTimeout(function(){
resolve(x * 2)
}, 1000)
})
}
getData(20).then((res) => {
getData(res);
})
//我们可以使用promise去实现,但是链式结构会让代码变的比较难懂
//我们试着使用generator去实现以下
function* gen(){
const p1 = yield getData(20);
console.log(p1, 'p1');
const p2 = yield getData(40);
console.log(p2, 'p2');
}
const iter1 = gen();*/
iter1.next().value.then((res) => {
iter1.next().value.then((res) => {
iter1.next();
})
})
使用next也可以实现,但是要通过next去层层调用,当然我们可以通过递归去实现
function* gen(){
const p1 = yield getData(20);
console.log(p1, 'p1');
const p2 = yield getData(p1);
console.log(p2, 'p2');
}
function runner(generator){
let g = generator();
function next(data){
let result = g.next(data); //yield getData(20); yield getData(40)
console.log(result, 'result'); //{value: promise, done: false} {}
if(result.done){
return result.value;
}
result.value.then(function(data){
next(data);
})
}
next();
}
runner(gen);
//但是这样也很麻烦,我们可以试着使用async/await直接实现
async function myBz2(){
const a = await getData(20);
console.log(a);
const b = await getData(a);
console.log(b);
}
myBz2();//这样子看,其实async/await更像是generator的再次封装