Generator

JS异步

函数回调

当我们进行异步嵌套时,就会写出如下代码。这类代码会随着嵌套层数的增长,维护难度几何上升。

step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
      step4(value3, function(value4) {
        // Do something with value4
      });
    });
  });
});

Promise

为了解决异步嵌套的问题,机智的人们推出了Promise方案,实质上也只是将嵌套碾平,并没有解决异步带来的难以捕获错误问题。

Q.fcall(step1)
  .then(step2)
  .then(step3)
  .then(step4)
  .then(function (value4) {
    // Do something with value4
  }, function (error) {
    // Handle any error from step1 through step4
  })
  .done();

错误处理

传统语言的错误处理

public static boolean catchTest() {
        try {
            // 远程获取数据 
        } catch (Exception e) {
            System.out.println(" -- Exception --");
            return catchMethod();    
        }
    }

JS的错误处理

    try{
        $.get('http://0.0.0.0:8082')
    }catch (error){
        // 并不能捕获到异步的异常
        console.error(error)
    }

为了解决上述的异步问题,诞生了generator

介绍generator

Generator函数是ES6提供的一种异步编程解决方案。可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。

如何使用generator

创建generator函数

形式上,Generator函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield语句,定义不同的内部状态

// *必须跟在function在后面,至于空格有没有有几个都不关心
function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

使用generator函数

function *gen(name){
    var yieldName = yield name;
    console.log('name:'+name);
    console.log('yieldName:'+yieldName);
    return yieldName;
}

var g = gen('lili');

// Object {value: "lili", done: false}
var first = g.next();

// Object {value: "sandy", done: true}
var second = g.next('sandy');

yield

yield其实是一个暂停标志,当引擎执行到yield这句话时generator函数暂停执行,将执行权限交给调用g.next()的函数,yield 后面的值会作为{value,done}中的value一起返回给g.next();

next

  1. 像普通函数一样执行gen('lili'),传入'lili'参数
  2. g.next(); 触发generator函数执行,碰到yield name停住,并返回{value: "lili", done: false}
  3. g.next('sandy') 将'sandy'传入generator函数,相当于var yieldName = 'sandy';后面没有yield函数,所以一直执行到return yildName;所以g.next('sandy')返回{value: "sandy", done: true}执行结束

this

取决于generator执行位置,跟next执行的位置无关

function *genThis(){
    // true
    console.log(this === window);
}

var g = genThis();
g.next();
var obj = {
    a:1,
    b:2,
    gen: function*(){
        // 1 2
        console.log(this.a,this.b);
    }
}

var g = obj.gen();
g.next();

Generator.prototype.throw()

Generator函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在Generator函数体内捕获。

var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b');
} catch (e) {
  console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

上面代码中,遍历器对象i连续抛出两个错误。第一个错误被Generator函数体内的catch语句捕获。i第二次抛出错误,由于Generator函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了Generator函数体,被函数体外的catch语句捕获。

//一个回调函数处理错误的例子
foo('a', function (a) {
  if (a.error) {
    throw new Error(a.error);
  }

  foo('b', function (b) {
    if (b.error) {
      throw new Error(b.error);
    }

    foo('c', function (c) {
      if (c.error) {
        throw new Error(c.error);
      }

      console.log(a, b, c);
    });
  });
});
//用generator改写
function* g(){
  try {
    var a = yield foo('a');
    var b = yield foo('b');
    var c = yield foo('c');
  } catch (e) {
    console.log(e);
  }

  console.log(a, b, c);
}

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 }

如果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 }

yield*

如果在Generater函数内部,调用另一个Generator函数,默认情况下是没有效果的。

function* inner() {
  yield 'hello!';
}

function* outer1() {
  yield 'open';
  yield inner();
  yield 'close';
}

var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"

其实yield*可以看做是for-of的语法糖,当然这种语法糖也会丢失return的返回值

function* outer2() {
  yield 'open'
  yield* inner()
  yield 'close'
}

function* outer2() {
  yield 'open'
  for(let v in inner())
    yield v;
  yield 'close'
}

async/await

async 可以声明一个异步函数,此函数需要返回一个 Promise 对象。await 可以等待一个 Promise 对象 resolve,并拿到结果。

function sleep(timeout) {  
  return new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve();
    }, timeout);
  });
}

(async function() {
  console.log('Do some thing, ' + new Date());
  await sleep(3000);
  console.log('Do other things, ' + new Date());
})();

作用域链

作用域

深入generator原理

深入generator原理