异步编程之Generator(2)——剖析特性
异步编程系列教程:
- (翻译)异步编程之Promise(1)——初见魅力
- 异步编程之Promise(2):探究原理
- 异步编程之Promise(3):拓展进阶
- 异步编程之Generator(1)——领略魅力
- 异步编程之Generator(2)——剖析特性
- 异步编程之co——源码分析
Generator基础
继上一篇见识过其配合promise带来的超爽的异步编程体验,我想应该大部分同学都会想好好看一下,到底这个Generator是什么?接下来我们会对Generator的特性进行剖析,让我们对接下来学习co源码打个扎实的基础。
起源
我们首先得知道,Generator一开始并不是用来做异步编程的,是后来的大牛们挖掘了它的特性,让它在异步编程里大放异彩。其实Generator是生成遍历器的构造器,ES6定义了一个遍历器的接口Iterator。任何数据结构满足Iterator接口,都可以统一实现遍历操作。一步一步的调用next()或者for..of循环都可以遍历实现Iterator接口的数据结构。
我们简单说一下遍历对象的next()是怎样的:
- 第一次调用
next()会直接指向第一个数据的位置,然后返回数据的信息。结构是这样的:{value: AnyType, done: Boolean}。value属性是指该数据的值,done则是标志是否已经true,结束了。 - 再一次调用
next()则指向下一个数据,返回相应的数据信息。 - 重复第二步,一直到数据结束,返回
{value: undefined, done: true}。则表示遍历已经全部完成。
这就是Iterator最基本的实现,当然这里是很片面的,若要展开说,基本又是一大篇文章可以写。这里就直接给出阮一峰老师关于Iterator的文章:10. Iterator和for...of循环
定义
在我们知道了Generator生成的遍历对象是什么之后,我们看一下如何定义这样的Generator函数。对上一篇有印象的同学,应该记得函数标识符后面有一个诡异的星号function* ()。其实这个星号在括号前也是没关系的,这里我是参考了co源码的。我们一旦定义了一个带星号的函数之后,用这个构造器生成的对象在harmony模式里就成了Generator对象(下面我会称其为遍历器)。我们可以测试一下一段代码。
var toString = Object.prototype.toString;
var Generator = function* (){
yield "hello";
yield "world";
};
var gen = Generator(); // 可以省去new来创建对象
console.log(toString.call(Generator)); // [object Function]
console.log(toString.call(gen)); // [object Generator]
这样我们通过调用特殊定义的Generator构造器,生成一个遍历器([object Generator])。那我们要遍历的话必须得知道遍历的每个成员,yield就是用来定义遍历成员的。也就是说,遍历器进行遍历的时候会以yield为间隔,一个yield一个成员,不断往下走直到不存在下一个yield。
在上面的例子中,就是第一次遍历到yield得到"hello",第二次继续执行遍历操作到yield得到"world",最后再执行就发现没有了,也就是done: true结束遍历。
接下来我们会详细说一下,遍历器是遍历的各种特性。
Generator特性
遍历
我们需要执行遍历,首先就是要得到遍历器。前面也说过了,就是调用Generator构造器生成的。然后该遍历器会有一个方法next()用来进行遍历操作,并且每一次的操作都会在yield处停止,并等待下一次的next()指令。我们看一看刚才的代码:
var Generator = function* (){
yield "hello";
yield "world";
};
var gen = new Generator();
console.log(gen.next()); // { value: 'hello', done: false }
console.log(gen.next()); // { value: 'world', done: false }
console.log(gen.next()); // { value: undefined, done: true }
我们可以看到最后当done: true时,value是undefined。其实我们return出去一个值,就会成为该value的值。其实换一个角度更加有意思,就是当你return出一个值,这个值必定是done: true。我们可以改一下上面的例子:
var Generator = function* (){
yield "hello";
return "world";
yield "!";
};
var gen = new Generator();
console.log(gen.next()); // { value: 'hello', done: false }
console.log(gen.next()); // { value: 'world', done: true }
console.log(gen.next()); // { value: undefined, done: true }
我们可以看到,如果遍历器去找感叹号的yield话,应该是value: '!'。但是因为提前return结束了遍历器,所以最后得到了 { value: 'world', done: true }。
yield传值
我们知道了每一次遍历器执行到yield处后,会把值放在一个对象中的属性中返回出去。但是我们在Generator构造器里怎么利用这个值呢?其实我们可以为遍历器的next(res)传入一个参数,这个参数将会成为这一次yield的值。乍一看,好像不大清楚,看看代码就懂了。
var Generator = function* (){
var hello = yield "hello";
console.log(hello); // hi
var world = yield "world";
console.log(world); // undefined
};
var gen = new Generator();
var first = gen.next("nothing");
var second = gen.next("hi");
var third = gen.next();
我们第一次next()相当于启动器,这个时候传入任何参数都是被忽略的,因为这个参数无法作为上一个yield的值(没有上一个)。到我们第二次的next("hi"),传入了一个"hi"字符串,这个参数就成为了yield的值,直接赋值给hello变量并打印出来。我们最后一个world变量是undefined,是因为next()并没有传入任何参数。可以这么说,每一次遍历器遍历得到的成员的值,和yield的值是没有必然联系的。
所以我们看代码的执行顺序也是很有趣的一件事,遍历器会执行到语句yield右侧即停止。等到下一次next()启动,然后才会根据yield得到的值,对语句左侧变量进行赋值。这样想的话,如果我们下一次yield语句,依赖第一次的值,我们就需要在next()里传入上一次的value。我们对上一次的代码做个小小的添加。
var first = gen.next("nothing");
var second = gen.next("hi");
var third = gen.next(second.value); //构造函数的world变量值也会是"hi"。
这个是Generator非常重要的特性,下去要好好实践一番,加深印象。接下来co源码分析,这个特性配合promise可以放华丽的大招。
遍历遍历器里的遍历器
我起这个标题挺有意思的,哈哈哈。其实就和递归栈差不多,也就是说,当yield的是另一个遍历器,那么代码会进入到另一个遍历器里,直到结束后,才交回代码控制权。看一看咯:
var Generator = function* (){
yield "hello";
yield *anotherGen;
yield "world";
return "hello world";
};
var AnotherGenerator = function* (){
yield "强势插入!";
yield "不给hello world!";
}
var gen = new Generator();
var anotherGen = new AnotherGenerator();
console.log(gen.next()); // { value: 'hello', done: false }
console.log(gen.next()); // { value: '强势插入!', done: false }
console.log(gen.next()); // { value: '不给hello world!', done: false }
console.log(gen.next()); // { value: 'world', done: false }
console.log(gen.next()); // { value: 'hello world', done: true }
当我们需要遍历一个遍历器,那么*也是需要的,可以参考一下上面。
总结
我们知道了遍历对象遍历时得到的什么,还有next(res)传入参数有什么用,这对接下来的分析有着至关重要的作用。到这里,对Generator分析已经是差不多了。如果想要更深入了解的,可以去阮老师的博客看一看:11. Generator函数。
接下来一篇文章就是对co源码的分析,先预习和复习一些东西吧。我们回顾一下promise,我们在将一个异步操作promise化后,当我们调用这个异步操作,我们会得到一个promise对象。所以我们可以想象一下:
- 我们调用遍历器的
next()得到该异步的promise对象 - 在promise对象的
then()中的resolve对数据进行处理 - 把数据作为参数传入
next(res),进行下一次异步操作 - 直到迭代器的
done: true,结束遍历。
这样我们就可以一环扣一环的将Generator函数里的异步操作进行迭代,形成一种异步编程同步写法的优良体验。当然我们这里不会详细说,如何去实现,因为我会在下一篇好好讲讲。
异步编程之Generator(2)——剖析特性的更多相关文章
- 异步编程之Generator(1)——领略魅力
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
- javascript异步编程之generator(生成器函数)与asnyc/await语法糖
Generator 异步方案 相比于传统回调函数的方式处理异步调用,Promise最大的优势就是可以链式调用解决回调嵌套的问题.但是这样写依然会有大量的回调函数,虽然他们之间没有嵌套,但是还是没有达到 ...
- 异步编程之Promise(3):拓展进阶
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
- 异步编程之Promise(2):探究原理
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
- (翻译)异步编程之Promise(1):初见魅力
原文:https://www.promisejs.org/ by Forbes Lindesay 异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2) ...
- 异步编程之co——源码分析
异步编程系列教程: (翻译)异步编程之Promise(1)--初见魅力 异步编程之Promise(2):探究原理 异步编程之Promise(3):拓展进阶 异步编程之Generator(1)--领略魅 ...
- python异步编程之asyncio
python异步编程之asyncio 前言:python由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病.然而在IO密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率, ...
- net异步编程之await
net异步编程之await 初探asp.net异步编程之await 终于毕业了,也顺利进入一家期望的旅游互联网公司.27号入职.放肆了一个多月没写代码,好方啊. 另外一下观点均主要针对于await ...
- Javascript异步编程之setTimeout与setInterval详解分析(一)
Javascript异步编程之setTimeout与setInterval 在谈到异步编程时,本人最主要会从以下三个方面来总结异步编程(注意:特别解释:是总结,本人也是菜鸟,所以总结不好的,请各位大牛 ...
随机推荐
- vue做路由页面内容跳转
安装----npm npm install vue-router 如果在一个模块化工程中使用它,必须要通过 Vue.use() 明确地安装路由功能: import Vue from 'vue' imp ...
- fedora 27
安装 U盘安装,感谢学长的U盘 配置 安装外来软件的时候,需要输入root密码 软件 软件安装是个麻烦事儿啊 详情 [新手指南: 在 Ubuntu 和 Fedora 上安装软件包] Fedora 中文 ...
- 复选框checkbox样式修改
该方法只兼容IE9及以上 将checkbox和label关联起来, 将checkbox隐藏掉,通过点击label来点击checkbox,label的样式即可自定义. 通过checkbox:checke ...
- POJ2987 Firing 【最大权闭合图】
POJ2987 Firing Description You've finally got mad at "the world's most stupid" employees o ...
- WPF 中使用附加属性,将任意 UI 元素或控件裁剪成圆形(椭圆)
不知从什么时候开始,头像流行使用圆形了,于是各个平台开始追逐显示圆形裁剪图像的技术.WPF 作为一个优秀的 UI 框架,当然有其内建的机制支持这种圆形裁剪. 不过,内建的机制仅支持画刷,而如果被裁剪的 ...
- iOS客户端打包自动集成weex方案
我司在2017年已经部分使用weex开发,然而开发weex 的人都知道,在前端开发完成之后需要集成到 安卓和iOS 各个app 之中.每次修改都要重新copy给各个app 负责人去打包,因此这是一种重 ...
- python函数参数总结
python中函数参数有:默认参数.关键字参数.非关键字可变长参数(元组).关键字可变长参数(字典) 默认参数:在函数声明时,指定形参的默认值,调用时可不传入改参数(使用默认值)def foo(x): ...
- grpc 安装以及墙的解决方法
1. 默认官方文档 go get -u google.golang.org/grpc 因墙的问题,大部分安装是无法完成的 2. 解决方法 a. grpc mkdir -p $GOAPTH/src/go ...
- jsp_include
jsp__include指令先包含后编译 include 行为 先编译后包含 <jsp:include page="head.jsp"></jsp:include ...
- PHP如何实现网址伪静态(转)
Apache的 mod_rewrite是比较强大的,在进行网站建设时,可以通过这个模块来实现伪静态.主要步骤如下: 1.检测Apache是否开启mod_rewrite功能 可以通过php提供的 ...