generator生成器的设计原理和async语法糖的应用

24 篇文章 0 订阅

 

 

 

generator生成器的设计原理:

  1. 状态机,简化函数内部状态存储;
  2. 半协程实现
  3. 上下文冻结

应用场景:

  1. 异步操作的同步化表达
  2. 控制流管理
  3. 部署 Iterator 接口
  4. 作为数据结构

首先介绍几个概念:

coroutine美: [kəru'tin] n.联立程序;协同程序

function* asyncJob() {  //协程的简单实现
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}

整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)

————————————————————————————————————————————————

generator 美: ['dʒenə.reɪtər] n.发电机;发生器;电力公司

首先介绍一下generator的意思,原意为发生器,在es6中是生成器的意思。

generator生成器和iterator遍历器是对应的,我们知道iterator遍历器是给不同数据结构提供统一的数据接口机制,那么相对的generator生成器是生成这样一个遍历器,进而使数据结构拥有iterator遍历器接口。换一种方法来说,generator函数提供了可供遍历的状态,所以generator是一个状态机,在其内部封装了多个状态,这些状态可以使用iterator遍历器遍历。

在形式上,generator函数有两个特征:

1,function关键字和函数名之间有一个*号:function* myGenerator(){}

2,,在函数内部使用yield关键字;yield英: [jiːld] v.提供,产生

function* myGenerator(){
  yield 'hello';
  yield 'world';
  return 'ending'
}
//该函数有三个状态:hello,world 和 return 语句

注意:既然generator是一个状态机,所以直接运行myGenerator()函数,并不会执行,相反的是生成一个指向内部状态的指针对象,即一个可供遍历的遍历器。

想运行generator,必须调用遍历器对象的next方法,使得指针移向下一个状态,直到遇到下一个yield表达式(或return语句)为止。

所以就可以得出generator函数的特征:

Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
const test = helloWorldGenerator();

test.next()
// { value: 'hello', done: false }

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

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

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

另外:Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。

function* f() {
  console.log('执行了!')
}

var generator = f();

setTimeout(function () {
  generator.next()
}, 2000);

与 Iterator 接口的关系

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象;

设置Symbol.iterator方法的函数,就等于设置对象的遍历器生成函数,所以可以将generator函数赋值给Symbol.iterator方法。

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

[...myIterable] // [1, 2, 3]

由于generator生成器执行后生成一个遍历器对象,该遍历器对象也具有Symbol.iterator方法,而对应的值就是generator()

function* mygenerator(){
  yield 1
}

const g = mygenerator()

g[Symbol.iterator]() === g
//g[Symbol.iterator]返回一个遍历器生成函数,执行后生成遍历器
//g是由generator()执行过生成的遍历器对象,二者相等

next方法的参数

yield表达式本身没有返回值,或者说总是返回undefined

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 }
当next传入true参数后,这时上一个yield的值会被重置为true,这时reset = true,执行next方法时,
由于reset已经等于true,此时i= -1,执行i++ => 执行 yield 0 => 0;reset变为undefined

注意:

1,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。

2,V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。

3,从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

next()方法入参的意义:

Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。

通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值

function* dataConsumer() {
  console.log('Started');
  console.log(`1. ${yield}`);
  console.log(`2. ${yield}`);
  return 'result';
}

let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')

如果想第一次就给next方法传参,可以给generator函数包一层,在外部函数中执行第一次next方法:

function* myGenerator(){
  console.log(`${yield}`)
}

function wrapper(targetGenerator){
  return function(...args){
    let generatorObject = targetGenerator();
    generatorObject.next();
    return generatorObject;
  }
}

//返回一个函数,该函数返回一个已经执行过第一次next()方法的遍历器对象。
const wrapperMyGenerator = wrapper(myGenerator);

const nowGenerator = wrapperMyGenerator();

nowGenerator.next('测试') //测试

generator的for...of...循环

该方法对于generator来说,注意一点,当generator返回值done === true时,循环结束,且循环不包括该返回对象。

从上面代码可见,使用for...of语句时不需要使用next方法。

给对象添加iterator的方法:

function* objectEntries(obj) {
  let propKeys = Reflect.ownKeys(obj);

  for (let propKey of propKeys) {
    yield [propKey, obj[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

for (let [key, value] of objectEntries(jane)) {
  console.log(`${key}: ${value}`);
}

还可以赋值给对象的Symbol.iterator方法

function* objectEntries() {
  let propKeys = Object.keys(this);

  for (let propKey of propKeys) {
    yield [propKey, this[propKey]];
  }
}

let jane = { first: 'Jane', last: 'Doe' };

jane[Symbol.iterator] = objectEntries;

for (let [key, value] of jane) {
  console.log(`${key}: ${value}`);
}

return语句结束generator内部的状态,执行到return 再执行返回undefined

Generator的原型方法:

Generator.prototype.throw(),Generator.prototype.return()

throw() 在函数体外抛出错误,然后在 Generator 函数体内捕获

return():返回给定的值,并且终结遍历 Generator 函数

next()、throw()、return() 的共同点

作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式(带入参)

  • next()是将yield表达式替换成一个值
  • throw()是将yield表达式替换成一个throw语句
  • return()是将yield表达式替换成一个return语句

async函数:

实现原理:

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () {
    // ...
  });
}

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}

异步遍历器:

asyncIterator
  .next()
  .then(
    ({ value, done }) => /* ... */
  );

由于异步遍历器的next方法,返回的是一个 Promise 对象。因此,可以把它放在await命令后面。

async function f() {
  const asyncIterable = createAsyncIterable(['a', 'b']);
  const asyncIterator = asyncIterable[Symbol.asyncIterator]();
  console.log(await asyncIterator.next());
  // { value: 'a', done: false }
  console.log(await asyncIterator.next());
  // { value: 'b', done: false }
  console.log(await asyncIterator.next());
  // { value: undefined, done: true }
}

for await...of

前面介绍过,for...of循环用于遍历同步的 Iterator 接口。新引入的for await...of循环,则是用于遍历异步的 Iterator 接口。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值