【深入学习JS】12_生成器(Generator)

基本概念

可以将Generator理解成状态机,里面封装了多个内部状态。

function* testGenerator(){
    yield '开始';
    yield '暂停';
    yield '继续';
    yield '结束';
}

var test = testGenerator();
console.log(test.next());   // { value: '开始', done: false }
console.log(test.next());   // { value: '暂停', done: false }
console.log(test.next());   // { value: '继续', done: false }
console.log(test.next());   // { value: '结束', done: false }
console.log(test.next());   // { value:  undefined, done: true }

Generator跟普通函数相比,在声明时候多携带了*,其次内部使用了yield

当调用testGenerator时,并不会执行函数内部的功能,而是返回一个指向内部状态的指针。只有当调用指针的next()时,就会执行函数内部的功能,指导遇到yieldreturn为止。下一次的next则会从上一次停止的yield方法继续往下执行,直到遇到下一个yield为止。

故调用testGenerator()返回了一个状态指针,并保存在了test变量中。第一次调用next时,执行到了yield '开始',则输出了开始并结束。第二次调用yield时,从上一次结束的位置继续往下,遇到了yield '暂停',输出暂停并结束。依次类推。直到最后一次next()被调用时,由于内部已经没有yield方法,所以value: undefined, done: true

与Iterator的关系

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

console.log(...myIterable) // 1 2 3

for(key of myIterable){
  console.log(key);        // 1 2 3
}

next

yield本身是没有返回值的,但是我们可以通过nextyield传递返回值。next方法携带的参数会被当作上一个yield表达式的返回值。

function* test() {
  for(let i = 0; true; i++){
    var reset = yield i;
    console.log(`第${i}次reset=${reset}`);
  }
};

var t = test();
t.next(1);
t.next(2);  // 第0次reset=2
t.next(3);  // 第1次reset=3
  • 执行t.next(1)时,执行到了yield 0,结束;
  • 执行t.next(2)时,由于它存在上一个yield表达式,则此时reset=2,执行yield 2结束;
  • 执行t.next(3)时,由于它存在上一个yield表达式,则此时reset=3,执行yield 3结束。

注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

参考ES6书上的例子:

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

//  =================错误的做法==============
var a = foo(5); 
// 此时执行 yield(5+1), 所以输出{value: 6, done: false}
console.log(a.next());  
// y依靠的是上一次的yield的返回值,由于这次没有给next传参,所以上一次yield(x+1)的返回值是undefined
// y = undefined, 则执行yield(undefined/3),则为{value: NaN, done: false}
console.log(a.next());  
// z依靠的是上一次的yield的返回值,由于这次没有给next传参,所以上一次yield (y / 3)的返回值是undefined
// z = undefined, 执行5+undefined+undefined = NaN, 则为{value: NaN, done: false}
console.log(a.next()); 
function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}
//  =================正确的做法==============
var b = foo(5);
// 此时执行 yield(5+1), 所以输出{value: 6, done: false}
var res1 = b.next();
console.log(res1.value);       // 6
// y依靠的是上一次的yield的返回值,由于这次给next传参6,所以上一次yield(x+1)的返回值是6
// y = 12, 则执行yield(12/3),则为{value: 4, done: false}
var res2 = b.next(res1.value);
console.log(res2.value);      // 4
// z依靠的是上一次的yield的返回值,由于这次给next传参4,所以上一次yield (y / 3)的返回值是4
// z = 4, 执行5+12+4 = NaN, 则为{value: 21, done: false}
var res3 = b.next(res2.value);
console.log(res3.value);     // 21

实际应用

协程

猪肚鸡的流程是:准备工作、炒鸡、炖鸡、上料、上桌,其中:

  • 主厨负责: 炒鸡、上料;
  • 伙计负责: 准备工作、炖鸡、上桌。
function* chef(){
    console.log('炒鸡');
    yield "worker";
    console.log('上料');
    yield "worker";  
}

function* worker(){
    console.log('准备工作');
    yield "chef";
    console.log('炖鸡');
    yield "chef";  
    console.log('上桌');
}

var ch = chef();
var wo = worker();

function run(gen){
    var v = gen.next();
    if(v.value === 'chef'){
       run(ch);
    }else if(v.value =="worker"){
      run(wo);
   }
}

run(wo);

异步编程同步化

function fetchData(url, cb){
    return function(cb){
        setTimeout(function(){
            cb({status: 200, data: url})
        }, 1000)
    }
}

function* gen(){
    var r1 = yield fetchData('https://api.github.com/users/github');
    var r2 = yield fetchData('https://api.github.com/users/github/followers');
    
    console.log([r1.data, r2.data].join('\n'));
}

function run(gen){
    var g = gen();
    
    function next(data){
        var result = g.next(data);
        if(result.done) return;
        
        // 这里的value就是fetchData返回的回调函数,所以会执行next
        result.value(next);
    }
    
    next();
}

run(gen);

在这里封装了run函数,可以将run理解成是一个执行器,自动执行Generator函数。Generator+执行器就可以实现async/await。这个后面学习async/await会记录一下。

参考

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值