Generator & yield

开局官宣:sec-generatoryield,这是对yield的介绍。

同样巴拉巴拉列了9条,将以上链接中的说明简化成3条:

1. 在GeneratorFunction内,当遇到yield关键字的时候,先将执行上下文设置为yield之后的表达式进行执行,并且将该表达式返回值作为当前迭代的结果;

2. 将gen上下文从上下文堆栈中移除,将上级(gen之外)上下文(依次)恢复为当前执行的上下文,此过程中需设置gen上下文的评估状态,以便在上下文恢复时(下一次调用.next)继续操作迭代;

3. 通过.next方法依次执行迭代器。

先对上面3点有点印象,再来看看 Generator。

Generator 对象是通过 GeneratorFunction 执行返回的对象,具有可迭代的特性(迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值),关于迭代器详见"迭代器"

  1. GeneratorFunction => Generator
  2.  
  3. GeneratorFunction.prototype
  4. next 返回迭代结果
  5. return 传入参数作为迭代结果的value并返回该迭代项,并且结束Generator对象的迭代
  6. throw 抛出错误值,并且结束Generator对象的迭代

每个迭代结果都包含 done 和 value :

  1. done 表示生成器是否被完成;

  2. value 表示当前的值。

来个例子:

  1. function* iterator1(){
  2. console.log(1);
  3. yield '1';
  4. console.log(2)
  5. yield *iterator2();
  6. yield '2';
  7. console.log(3);
  8. }
  9.  
  10. function* iterator2(){
  11. yield '3';
  12. console.log(4);
  13. yield '4';
  14. }
  15.  
  16. function fn1(){
  17. console.log(5)
  18. fn2();
  19. console.log(6)
  20. }
  21.  
  22. function fn2(){
  23. console.log(7)
  24. var iter = iterator1();
  25. console.log(iter.next());
  26. console.log(iter.next());
  27. console.log(iter.next());
  28. console.log(8);
  29. console.log(iter.next());
  30. console.log(iter.next());
  31. console.log(9);
  32. }
  33. fn1();
  34. /*
  35. * 输出顺序
  36. * var iter = iterator1(); // before : 5 7
  37. * console.log(iter.next()); // 1 {value:1,done:false}
  38. * console.log(iter.next()); // 2 {value:3,done:false}
  39. * console.log(iter.next()); // 4 {value:4,done:false}
  40. * console.log(8); // 8
  41. * console.log(iter.next()); // {value:2,done:false}
  42. * console.log(iter.next()); // 3 {value:undefined,done:true}
  43. * console.log(9); // 9 after : 6
  44. */

看输出顺序(多个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(),还是进入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以执行迭代。

如下面例子:

  1. function run(gen){
  2. const task = gen();
  3. // 定义一个对象用于存每个迭代结果,传入result.value 赋值到指定对象上
  4. let result = task.next();
  5.  
  6. // 如果迭代未结束,则继续执行next(),获取下个迭代结果,以此类推...
  7. function step(){
  8. if(!result.done){
  9. result = task.next(result.value);
  10. step();
  11. }
  12. }
  13. step();
  14. }
  15.  
  16. run(function*(){
  17. let i = 0;
  18. while(i<10) {
  19. yield ++i,
  20. console.log(i);
  21. }
  22. });
  23. // 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,done:false},打印 2;

  3. 在step内,result={value:3,done:false},打印 3;

  ...

  10. 在step内,result={value:10,done:false},打印 10;

  11. 在step内,result={value:undefined,done:true},迭代对象被完成。

如果yield后跟的是异步表达式呢?

代码如下:

  1. // 基于上面的run函数
  2. run(function*(){
  3. const value1=yield fn1();
  4. console.log('v1',value1);
  5. const value2 = yield fn2();
  6. console.log('v2',value2)
  7. })
  8. function fn1(){
  9. const promise = new Promise(resolve => setTimeout(()=> resolve(' success'),3000));
  10. promise.then(res=> console.log(res) )
  11. return promise;
  12. };
  13. function fn2(){
  14. console.log('fn2');
  15. return 'fn2';
  16. }
  17. // v1 Promise
  18. // fn2
  19. // v2 fn2
  20. // 3s 后 success

假如需求需要fn2的执行依赖fn1的异步返回值,简单改造一下run执行器试试:

  1. // 修改上面的run函数
  2. function run(gen){
  3. const iter = gen();
  4. // result用来存储每一次迭代结果
  5. let result = iter.next();
  6.  
  7. step();
  8.  
  9. function step(){
  10. // 如果迭代对象未完成
  11. if(!result.done){
  12. // 如果是Promise,则在.then之后执行next
  13. if(result.value instanceof Promise){
  14. result.value.then(res =>{
  15. result = iter.next(res);
  16. step();
  17. })
  18. }else{
  19. result = iter.next(result.value);
  20. step();
  21. }
  22. }
  23. }
  24. }

以上是没看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摘出来后的代码:

  1. var run = function (thisArg,_arguments,generator) {
  2. return new Promise(function (resolve, reject) {
  3. generator = generator.apply(thisArg, _arguments || [])
  4.  
  5. function fulfilled(value) {
  6. try {
  7. step(generator.next(value));
  8. } catch (e) {
  9. reject(e);
  10. }
  11. }
  12.  
  13. function rejected(value) {
  14. try {
  15. step(generator["throw"](value));
  16. } catch (e) {
  17. reject(e);
  18. }
  19. }
  20.  
  21. step(generator.next());
  22.  
  23. function step(result) {
  24. result.done ? resolve(result.value) : new Promise(function (resolve) {
  25. resolve(result.value);
  26. }).then(fulfilled, rejected);
  27. }
  28. });
  29. };

可能有些关联的文章:

理解 async/await 的执行

分步理解 Promise 的实现

Generator关系图

co

文章仅供参考!!!关于更多Generator知识,以阅读文章开头官方文档为准,如更多的术语以及它们各代表什么过程...

学习过程中,多写几次总是会记得深刻些。

理解 Generator 的执行的更多相关文章

  1. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

  2. 深入理解JavaScript系列+ 深入理解javascript之执行上下文

    http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html http://blog.csdn.net/hi_kevin/article/d ...

  3. 前端知识体系:JavaScript基础-原型和原型链-理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题

    理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题(原文文档) 1.什么是执行上下文: 简而言之,执行上下文就是当前JavaScript代码被解析和执行时所在环境的抽象概念,Java ...

  4. 【CUDA 基础】3.2 理解线程束执行的本质(Part I)

    title: [CUDA 基础]3.2 理解线程束执行的本质(Part I) categories: CUDA Freshman tags: 线程束分化 CUDA分支 toc: true date: ...

  5. 深入理解JS:执行上下文中的this(二)

    目录 序言 Function.prototype.bind() 方法 箭头函数 参考 1.序言 在 深入理解JS:执行上下文中的this(一) 中,我们主要深入分析全局环境和函数环境中函数调用的 th ...

  6. 从底层理解Python的执行

    摘要:是否想在Python解释器的内部晃悠一圈?是不是想实现一个Python代码执行的追踪器?没有基础?不要怕,这篇文章让你初窥Python底层的奥妙. [编者按]下面博文将带你创建一个字节码级别的追 ...

  7. 理解Javascript之执行上下文(Execution Context)

    1>什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码 - 当执行一 ...

  8. 理解JS的执行环境

    执行环境(Execution context,EC)或执行上下文,是JS中一个极为重要的概念 EC的组成 当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文会构成了一个执行上 ...

  9. 理解 Python 的执行方式,与字节码 bytecode 玩耍 (下)

    上次写到,Python 的执行方式是把代码编译成bytecode(字节码)指令,然后由虚拟机来执行这些 bytecode 而 bytecode 长成这个样子:  b'|\x00\x00d\x01\x0 ...

随机推荐

  1. Ubuntu下安装pycharm并设置快捷方式

    作者:tongqingliu 转载请注明出处:http://www.cnblogs.com/liutongqing/p/7070327.html Ubuntu下安装pycharm并设置快捷方式 下载P ...

  2. C#-VS发布网站-准备待发布网站-摘

    通过使用“发布网站”工具部署网站项目 准备网站源文件 在vs生成发布文件 配置IIS   .NET Framework 4 其他版本 Visual Studio 2008 Visual Studio ...

  3. centos设置服务开机自动启动的方法

    centos安装好apache,mysql等服务器程序后,并没有设置成开机自动启动的,为避免重启后还要手动开启web等服务器,还是做下设置好,其实设置很简单,用chkconfig命令就行了. 例如要开 ...

  4. kafka讲解

    转载http://www.jasongj.com/2015/01/02/Kafka深度解析 Kafka是Apache下的一个子项目,是一个高性能跨语言分布式发布/订阅消息队列系统,而Jafka是在Ka ...

  5. express package.json解析

    教程:http://www.tuicool.com/articles/vuiyIz

  6. DXP中插入LOGO图片方法(1)

    DXP中插入LOGO图片方法 1.QQ截图后,打开“开始”-->"附件"——>"画图工具",如图: 2.另存为BMP文件格式(设置图片大小.黑白色即 ...

  7. Android-Kotlin-区间与for&List&Map简单使用

    区间与for: package cn.kotlin.kotlin_base04 /** * 区间与for */ fun main(args: Array<String>) { /** * ...

  8. ASP.NET中实现回调

    一.引言 在ASp.NET网页的默认模型中,用户通过单击按钮或其他操作的方式来提交页面,此时客户端将当前页面表单中的所有数据(包括一些自动生成的隐藏域)都提交到服务器端,服务器将重新实例化一个当前页面 ...

  9. C#之WinForm设置控件居中

    简单阐述 在C#的WinForm里面,原生控件是没有居中属性的,故通过重写OnResize(EventArgs e)方法,通过计算,重新定位控件位置. 以Label控件为例 (1)将label的Aut ...

  10. Ubuntu18.04 - 实现鼠标右键新建文件功能!

    Ubuntu18.04安装完毕后,你会发现,如果在桌面或其它地方,像在Windows下鼠标右键,新建一个文件,那么真的不行,没有那个选项!这个功能其实非常有用,怎么实现呢?新建一个你要右键新建类型文件 ...