Generator函数的使用和原理

简介

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同 。

在Javascript中,一个函数一旦开始执行,就会运行到最后或遇到return时结束,运行期间不会有其它代码能够打断它,也不能从外部再传入值到函数体内

而Generator函数(生成器)的出现使得打破函数的完整运行成为了可能,其语法行为与传统函数完全不同

 

可迭代协议

可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,比如 Array 或者 Map,而其他内置类型则不是(比如 Object))。

要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性: 

Symbol.iterator] 一个无参数的函数,其返回值为一个符合迭代器协议的对象。

迭代器协议

迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。

只有实现了一个拥有以下语义(semantic)的 next() 方法,一个对象才能成为迭代器:

next

一个无参数函数,返回一个应当拥有以下两个属性的对象:

done(boolean)

如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定  done 这个属性。)

如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。

value

迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

next() 方法必须返回一个对象,该对象应当有两个属性: done 和 value,如果返回了一个非对象值(比如 false 或 undefined),则会抛出一个 TypeError 异常("iterator.next() returned a non-object value")。

String 是一个内置的可迭代对象:

let someString = "hi";
typeof someString[Symbol.iterator];   
let iterator = someString[Symbol.iterator]();
iterator + "";                               // "[object String Iterator]"
 
iterator.next();                             // { value: "h", done: false }
iterator.next();                             // { value: "i", done: false }
iterator.next();                             // { value: undefined, done: true }

可迭代对象

function createIterator(arr) {
  var i = 0
  return {
    next: function() {
      var done = i >= arr.length;
      var value = !done ? arr[i] : undefined;
      i++
      return {value, done};
    }
  } 
}
var interator = createIterator([1,3,4,3])

调用函数后生成一个可迭代对象,上面有next方法,返回{value, done}

 
console.log(interator.next()) // {value: 1, done: false}
console.log(interator.next()) // {value: 3, done: false}
console.log(interator.next()) // {value: 4, done: false}
console.log(interator.next()) // {value: 3, done: false}
console.log(interator.next()) // {value: undefined, done: true}

使用

1直接只用

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

2 作为表达式

作为表达式使用 需要加括号,不然会报错

function * demo() {
  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}

var d = demo()
console.log(d.next())
console.log(d.next())
console.log(d.next())

3 yiel*

控制权交给另外一个生成器函数。调用生成器函数,返回值是一个带有

function *flat(arr) {
  for(var i = 0; i < arr.length; i ++) {
    if(typeof i === 'number') {
      yield i
    } else {
      yield* flat(i) // 交出迭代器的控制权
    }
  }
}
var f = flat(arr)
// console.log(f.next())
// console.log(f.next())
// console.log(f.next())
// console.log(f.next())



for(var j of f) {
  console.log(j)
}

 

console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())

这个时候循环失效。

4 next函数带参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

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


var a = foo(5);
console.log(a.next()) // {value: 6, done: false}
console.log(a.next(24)) // {value: 16, done: false}
console.log(a.next(20)) // {value: 73, done: true} 5 + 48 + 20

5 return 函数


function *genarator() {
    yield 1;
    yield 12;

}

var interators = genarator();

console.log(interators.next())
console.log(interators.return()) // 提前让生成器结束掉

6 throw函数

让生成器内部抛出错误

function *generator() {
  let a = 1;
  try {
    yield 3
  } catch (error) {
    a = 4
  }
  yield a + 1
}
var int = generator()

console.log(int.next()) // {value: 3, done: false}
console.log(int.throw()) // {value: 5, done: false}

实现原理

  • 一个线程存在多个协程
  • Generator函数是协程在ES6的实现
  • Yield挂起X协程(交给其它协程),next唤起协程

什么是协程呢?

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。

协程不是被操作系统内核所管理的,而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源。

协程不是进程也不是线程,而是一个特殊的函数,这个函数可以在某个地方挂起,并且可以重新在挂起处外继续运行。所以说,协程与进程、线程相比并不是一个维度的概念。

自动执行Generator函数

先来理解一下thunk函数。

调用一个读取文件的函数:


fs.readFile(filename, callback)

改成下面的方式也可以实现文件的读取:

var thunk = function(filename) {
  return function(callback) {
    return fs.readFile(filename, callback)
  }
}

thunk(filename)(callback)

这种就是thunk函数的写法,变成更通用的:

var thunk = function(fun) {
  return function() {
    var args = Array.prototype.slice.call(arguments)
    return function(callback) {
      
      fun.apply(null, args, callback)
    }
  }
}
var readFileThunk = thunk(fs.readFile)

 看起来没有多大用处,但是用到Generator函数中,就可以让它自动执行:

function* gen () {
  var result1 = yield readFileThunk('fileA')
  var result2 = yield readFileThunk('fileB')
  var result3 = yield readFileThunk('fileC')
  console.log('执行完毕')
}

function run(gen) {
  var g = gen();

  var next = function() {
    var result = g.next()
    if(result.done) return;
    result.value(next)
  }
  next()
}
run(gen)

thunk的函数的关键是需要有callback函数,需要在合适的时候调用callback才可以循环执行。

需要更详细的了解 Generator函数,可以参考文档:http://www.ruanyifeng.com/blog/2015/05/thunk.html

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值