面试之generator

806 阅读5分钟

一、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

上面代码中,foobar都是 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的再次封装