sec-generatoryield,这是对yield的介绍

本文深入解析Generator函数与yield关键字的工作原理,包括Generator对象的创建、迭代器协议、next方法使用及异步处理。通过实例演示如何利用Generator实现迭代任务执行器,解决依赖迭代结果的问题。

  开局官宣:sec-generatoryield,这是对yield的介绍。
  
  同样巴拉巴拉列了9条,将以上链接中的说明简化成3条:
  
  1. 在GeneratorFunction内,当遇到yield关键字的时候,先将执行上下文设置为yield之后的表达式进行执行,并且将该表达式返回值作为当前迭代的结果;
  
  2. 将gen上下文从上下文堆栈中移除,将上级(gen之外)上下文(依次)恢复为当前执行的上下文,此过程中需设置gen上下文的评估状态,以便在上下文恢复时(下一次调用.next)继续操作迭代;
  
  3. 通过.next方法依次执行迭代器。
  
  先对上面3点有点印象,再来看看 Generator。
  
  Generator 对象是通过 GeneratorFunction 执行返回的对象,具有可迭代的特性(迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值),关于迭代器详见"迭代器"。
  
  复制代码
  
  GeneratorFunction => Generator
  
  GeneratorFunction.prototype
  
  next 返回迭代结果
  
  return 传入参数作为迭代结果的value并返回该迭代项,并且结束Generator对象的迭代
  
  throw 抛出错误值,并且结束Generator对象的迭代
  
  复制代码
  
  每个迭代结果都包含 done 和 value :
  
  1. done 表示生成器是否被完成;
  
  2. value 表示当前的值。
  
  来个例子:
  
  复制代码
  
  function* iterator1(){
  
  console.log(1);
  
  yield '1';
  
  console.log(2)
  
  yield *iterator2();
  
  yield '2';
  
  console.log(3);
  
  }
  
  function* iterator2(){
  
  yield '3';
  
  console.log(4);
  
  yield '4';
  
  }
  
  function fn1(){
  
  console.log(5)
  
  fn2();
  
  console.log(6)
  
  }
  
  function fn2(){
  
  console.log(7)
  
  var iter = iterator1();
  
  console.log(iter.next());
  
  console.log(iter.next());
  
  console.log(iter.next());
  
  console.log(8);
  
  console.log(iter.next());
  
  console.log(iter.next());
  
  console.log(9);
  
  }
  
  fn1();
  
  /*
  
  * 输出顺序
  
  * var iter = iterator1();    // before : 5 7
  
  * console.log(iter.next());  // 1    {value:1,done:false}
  
  * console.log(iter.next());  // 2    {value:3,done:false}
  
  * console.log(iter.next());  // 4    {value:4,done:false}
  
  * console.log(8);            // 8
  
  * console.log(iter.next());  //      {value:2,done:false}
  
  * console.log(iter.next());  // 3    {value:undefined,done:true}
  
  * console.log(9);            // 9 after : 6
  
  */
  
  复制代码
  
  看输出顺序(多个Generator嵌套可看作为在外部Generator的某个索引位置插入内部Generator的元素作为迭代项):
  
  1. fn1被执行,首先输出 5;
  
  2. 进入fn2,输出 7;
  
  3. fn2中生成iter,并首次调用iter.next(),执行了iterator1里面第一个yield之前的console,输出 1,然后输出 {value: "1", done: false};
  
  4. 调用第二个iter.next(),进入iterator2中,输出 2,然后输出 {value:'3',done:false};
  
  5. 调用第三个iter.next(www.mhylpt.com/ ),还是进入iterator2,输出 4,然后输出 {value:'4',done:false};
  
  6. 调用fn2中的console.log(8),输出 8;
  
  7. 调用第四个iter.next(),这时候iterator2里面执行完了,继续执行iterator1的后续代码,输出 {value:2,done:false};
  
  8. 调用第五个iter.next(),继续iterator1的后续代码,输出 3,这时候iterator1的迭代结束,输出 {value:undefined,done:true};
  
  9. 调用fn2中的console.log(9),输出 9;
  
  10. 调用fn1中的console.log(6),输出 6。
  
  Generator的任务执行器
  
  Generator通过.next方法来依次做迭代的执行,然而每次都需要手动写方法调用是个问题。然后便有了迭代任务的执行器,在执行器内将主动调用.next以执行迭代。
  
  如下面例子:
  
  复制代码
  
  function run(gen){
  
  const task = gen();
  
  // 定义一个对象用于存每个迭代结果,传入result.value 赋值到指定对象上
  
  let result = task.next();
  
  // 如果迭代未结束,则继续执行next(),获取下个迭代结果,以此类推...
  
  function step(){
  
  if(!result.done){
  
  result = task.next(result.value);
  
  step();
  
  }
  
  }
  
  step();
  
  }
  
  run(function*(){
  
  let i = 0;
  
  while(i<10) {
  
  yield ++i,
  
  console.log(i);
  
  }
  
  });
  
  // 1 2 3 4 5 6 7 8 9 10
  
  复制代码
  
  在run(function*(/* ... */))中,先执行GeneratorFunction迭代对象返回Generator,然后用一个变量来存每次迭代结果...执行过程如下:
  
  1. result={value:1,done:false},打印 1;
  
  2. 在step内,result={value:2 www.dfgjyl.cn ,done:false},打印 2;
  
  3. 在step内,result={value:3,done:www.dfgjpt.com false},打印 3;
  
  ...
  
  10. 在step内,result={value:www.michenggw.com 10,done:false},打印 10;
  
  11. 在step内,result={value:www.gcyL157.com undefined,done:true},迭代对象被完成。
  
  如果yield后跟的是异步表达式呢?
  
  代码如下:
  
  复制代码
  
  // 基于上面的run函数
  
  run(function*(www.furong157.com){
  
  const value1=yield fn1(www.mingcheng178.com);
  
  console.log('v1',value1);
  
  const value2 = yield fn2();
  
  console.log('v2',value2)
  
  })
  
  function fn1(){
  
  const promise = new Promise(resolve => setTimeout(()=> resolve(' success'),3000));
  
  promise.then(res=> console.log(res) )
  
  return promise;
  
  };
  
  function fn2(){
  
  console.log('fn2');
  
  return 'fn2';
  
  }
  
  // v1 Promise
  
  // fn2
  
  // v2 fn2
  
  // 3s 后 success
  
  复制代码
  
  假如需求需要fn2的执行依赖fn1的异步返回值,简单改造一下run执行器试试:
  
  复制代码
  
  // 修改上面的run函数
  
  function run(gen){
  
  const iter = gen();
  
  // result用来存储每一次迭代结果
  
  let result = iter.next();
  
  step();
  
  function step(){
  
  // 如果迭代对象未完成
  
  if(!result.done){
  
  // 如果是Promise,则在.then之后执行next
  
  if(result.value instanceof Promise){
  
  result.value.then(res =>{
  
  result = iter.next(res);
  
  step();
  
  })
  
  }else{
  
  result = iter.next(result.value);
  
  step();
  
  }
  
  }
  
  }
  
  }
  
  复制代码
  
  以上是没看co代码之前针对问题"如果Generator对象迭代过程中某个迭代处理依赖上一个迭代结果该怎么办"想到的方法... 在实现方式上是差了些,但也可以用...
  
  co实现的更好看且适用,每次迭代(function, promise, generator, array, object)都包装成Promise处理,针对不同场景/类型则关注toPromise层的判断。
  
  对比一下ES7 async/await通过tsc --target es5 后的代码。
  
  1. 首先是个__awaiter方法,里面是 new Promise;
  
  2. 然后是个__generator方法,里面是GeneratorFunction。
  
  也是用Promise包装Generator的模式实现... 把__awaiter摘出来后的代码:
  
  复制代码
  
  var run = function (thisArg,_arguments,generator) {
  
  return new Promise(function (resolve, reject) {
  
  generator = generator.apply(thisArg, _arguments || [])
  
  function fulfilled(value) {
  
  try {
  
  step(generator.next(value));
  
  } catch (e) {
  
  reject(e);
  
  }
  
  }
  
  function rejected(value) {
  
  try {
  
  step(generator["throw"](value));
  
  } catch (e) {
  
  reject(e);
  
  }
  
  }
  
  step(generator.next());
  
  function step(result) {
  
  result.done ? resolve(result.value) : new Promise(function (resolve) {
  
  resolve(result.value);
  
  }).then(fulfilled, rejected);
  
  }
  
  });
  
  };
  
  复制代码
  
  可能有些关联的文章:
  
  理解 async/await 的执行
  
  分步理解 Promise 的实现
  
  Generator关系图
  
  co
  
  文章仅供参考!!!关于更多Generator知识,以阅读文章开头官方文档为准,如更多的术语以及它们各代表什么过程...
  
  学习过程中,多写几次总是会记得深刻些。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值