为什么要用generator

在前端开发过程中我们经常需要先请求后端的数据,再用拿来的数据进行使用网页页面渲染等操作,然而请求数据是一个异步操作,而我们的页面渲染又是同步操作,这里ES6中的generator就能发挥它的作用,使用它可以像写同步代码一样写异步代码。下面是一个例子,先忽略下面的写法,后面会详细说明。如果你已经理解generator基础可以直接跳过这部分和语法部分,直接看深入理解的部分。

function *foo() {// 请求数据var data = yield makeAjax('http://www.example.com');render(data);
}
复制代码

在等待数据的过程中会继续执行其他部分的代码,直到数据返回才会继续执行foo中后面的代码,这是怎么实现的那?我们都知道js是单线程的,就是说我们不可能同时执行两段代码,要实现这种效果,我们先来猜想下(ps:当然后面我都看过了,这里只是帮助大家理解,带着问题去看),我们来假设有一个“王杖”(指代cpu的执行权),谁拿到这个“王杖”,谁就可以做自己想做的事,现在代码执行到foo我们现在拿着“王杖”然后向服务器请求数据,现在数据还没有返回,我们不能干等着。作为王我们有着高尚的马克思主义思想,我们先把自己的权利交出去,让下一个需要用的人先用着,当然前提是要他们约定好一会儿有需要,再把“王杖”还给我们。等数据返回之后,我们再把我们的“王杖”要回来,就可以继续做我们想做的事情了。 如果你理解了这个过程,那么恭喜你,你已经基本理解了generator的运行机制,我这么比喻虽然有些过程不是很贴切,但基本是这么个思路。更多的东西还是向下看吧。

generator语法

generator函数

在用generator之前,我们首先要了解它的语法。在上面也看到过,它跟函数声明很像,但后面有多了个*号,就是function *foo() { },当然也可以这么写function* foo() { }。这里两种写法没有任何区别,全看个人习惯,这篇文章里我会用第一种语法。现在我们按这种语法声明一个generator函数,供后面使用。

function *foo() {}
复制代码

yield

到目前为止,我们还什么也干不了,因为我们还缺少了一个重要的老伙计yieldyield翻译成汉语是产生的意思。yield会让我们跟在后面的表达式执行,然后交出自己的控制权,停在这里,直到我们调用next()才会继续向下执行。这里新出现了next我们先跳过,先说说generator怎么执行。先看一个例子。

function *foo() {var a = yield 1 + 1;var b = yield 2 + a;console.log(b);
}var it = foo();
it.next();
it.next(2);
it.next(4);
复制代码

下面我们来逐步分析,首先我们定义了一个generator函数foo,然后我们执行它foo(),这里跟普通函数不同的是,它执行完之后返回的是一个迭代器,等着我们自己却调用一个又一个的yield。怎么调用那,这就用到我们前面提到的next了,它能够让迭代器一个一个的执行。好,现在我们调用第一个it.next(),函数会从头开始执行,然后执行到了第一个yield,它首先计算了1 + 1,嗯,然后停了下来。然后我们调用第二个it.next(2),注意我这里传入了一个2作为next函数的参数,这个2传给了a作为它的值,你可能还有很多其他的疑问,我们详细的后面再说。接着来,我们的it.next(2)执行到了第二个yield,并计算了2 + a由于a2所以就变成了2 + 2。第三步我们再调用it.next(4),过程跟上一步相同,我们把b赋值为4继续向下执行,执行到了最后打印出我们的b4。这就是generator执行的全部的过程了。现在弄明白了yieldnext的作用,回到刚才的问题,你可能要问,为什么要在next中传入24,这里是为了方便理解,我手动计算了1 + 12 + 2的值,那么程序自己计算的值在哪里?是next函数的返回值吗,带着这个疑问,我们来看下面一部分。

next

next的参数

next可以传入一个参数,来作为上一次yield的表达式的返回值,就像我们上面说的it.next(2)会让a等于2。当然第一次执行next也可以传入一个参数,但由于它没有上一次yield所以没有任何东西能够接受它,会被忽略掉,所以没有什么意义。

next的返回值

在这部分我们说说next返回值,废话不多说,我们先打印出来,看看它到底是什么,你可以自己执行一下,也可以直接看我执行的结果。

function *foo() {var a = yield 1 + 1;var b = yield 2 + a;console.log(b);
}var it = foo();
console.log(it.next());
console.log(it.next(2));
console.log(it.next(4));
复制代码

执行结果:

{ value: 2, done: false }
{ value: 4, done: false }
4
{ value: undefined, done: true }
复制代码

看到这里你会发现,yield后面的表达式执行的结果确实返回了,不过是在返回值的value字段中,那还有done字段使用来做什么用的那。其实这里的done是用来指示我们的迭代器,就是例子中的it是否执行完了,仔细观察你会发现最后一个it.next(4)返回值是done: true的,前面的都是false,那么最后一个打印值的undefined又是什么那,因为我们后面没有yield了,所以这里没有被计算出值,那么怎么让最后一个有值那,很简单加个return。我们改写下上面的例子。

function *foo() {var a = yield 1 + 1;var b = yield 2 + a;return b + 1;
}var it = foo();
console.log(it.next());
console.log(it.next(2));
console.log(it.next(4));
复制代码

执行结果:

{ value: 2, done: false }
{ value: 4, done: false }
{ value: 5, done: true }
复制代码

最后的nextvalue的值就是最终return返回的值。到这里我们就不再需要手动计算我们的值了,我们在改写下我们的例子。

function *foo() {var a = yield 1 + 1;var b = yield 2 + a;return b + 1;
}var it = foo();
var value1 = it.next().value;
var value2 = it.next(value1).value;
console.log(it.next(value2));
复制代码

大功告成!这些基本上就完成了generator的基础部分。但是还有更多深入的东西需要我们进一步挖掘,看下去,相信你会有收获的。

深入理解

前两部分我们学习了为什么要用generator以及generator的语法,这些都是基础,下面我们来看点不一样的东西,老规矩先带着问题才能更有目的性的看,这里先提出几个问题:

  • 怎样在异步代码中使用,上面的例子都是同步的啊
  • 如果出现错误要怎么进行错误的处理
  • 一个个调用next太麻烦了,能不能循环执行或者自动执行那

迭代器

进行下面所有的部分之前我们先说一说迭代器,看到现在,我们都知道generator函数执行完返回的是一个迭代器。在ES6中同样提供了一种新的迭代方式for...offor...of可以帮助我们直接迭代出每个的值,在数组中它像这样。

for (var i of ['a', 'b', 'c']) {console.log(i);
}// 输出结果
// a
// b
// c
复制代码

下面我们用我们的generator迭代器试试

function *foo() {yield 1;yield 2;yield 3;return 4;
}// 获取迭代器
var it = foo();for(var i of it) {console.log(i);
}// 输出结果
// 1
// 2
// 3
复制代码

现在我们发现for...of会直接取出我们每一次计算返回的值,直到done: true。这里注意,我们的4没有打印出来,说明for...of迭代,是不包括donetrue的时候的值的。

下面我们提一个新的问题,如果在generator中执行generator会怎么样?这里我们先认识一个新的语法yield *,这个语法可以让我们在yield跟一个generator执行器,当yield遇到一个新的generator需要执行,它会先将这个新的generator执行完,再继续执行我们当前的generator。这样说可能不太好理解,我们看代码。

function *foo() {yield 2;yield 3;yield 4;
}function * bar() {yield 1;yield *foo();yield 5;
}for ( var v of bar()) {console.log(v);
}
复制代码

这里有两个generator我们在bar中执行了foo,我们使用了yield *来执行foo,这里的执行顺序会是yield 1,然后遇到foo进入foo中,继续执行foo中的yield 2直到foo执行完毕。然后继续回到bar中执行yield 5所以最后的执行结果是:

1
2
3
4
5
复制代码

异步请求

我们上面的例子一直都是同步的,但实际上我们的应用是在异步中,我们现在来看看异步中怎么应用。

function request(url) {makeAjaxCall(url, function(response) {it.next(response);})
}function *foo() {var data = yield request('http://api.example.com');console.log(JSON.parse(data));
}var it = foo();
it.next();
复制代码

这里又回到一开头说的那个例子,异步请求在执行到yield的时候交出控制权,然后等数据回调成功后在回调中交回控制权。所以像同步一样写异步代码并不是说真的变同步了,只是异步回调的过程被封装了,从外面看不到而已。

错误处理

我们都知道在js中我们使用try...catch来处理错误,在generator中类似,如果在generator内发生错误,如果内部能处理,就在内部处理,不能处理就继续向外冒泡,直到能够处理错误或最后一层。

内部处理错误:

// 内部处理
function *foo() {try {yield Number(4).toUpperCase();} catch(e) {console.log('error in');}
}var it = foo();
it.next();// 运行结果:error in
复制代码

外部处理错误:

// 外部处理
function *foo() {yield Number(4).toUpperCase();
}var it = foo();
try {it.next();
} catch(e) {console.log('error out');
}// 运行结果:error out
复制代码

generator的错误处理中还有一个特殊的地方,它的迭代器有一个throw方法,能够将错误丢回generator中,在它暂停的地方报错,再往后就跟上面一样了,如果内部能处理则内部处理,不能内部处理则继续冒泡。

内部处理结果:

function *foo() {try {yield 1;} catch(e) {console.log('error', e);}yield 2;yield 3;
}var it = foo();
it.next();
it.throw('oh no!');// 运行结果:error oh no!
复制代码

外部处理结果:

function *foo() {yield 1;yield 2;yield 3;
}var it = foo();
it.next();
try {it.throw('oh no!');
} catch (e) {console.log('error', e);
}// 运行结果:error oh no!
复制代码

根据测试,发现迭代器的throw也算作一次迭代,测试代码如下:

function *foo() {try {yield 1;yield 2;} catch (e) {console.log('error', e);}yield 3;
}var it = foo();
console.log(it.next());
it.throw('oh no!');
console.log(it.next());// 运行结果
// { value: 1, done: false }
// error oh no!
// { value: undefined, done: true }
复制代码

当用throw丢回错误的时候,除了try中的语句,迭代器迭代掉了yield 3下次再迭代就是,就是最后结束的值了。错误处理到这里就没有了,就这么点东西^_^。

自动运行

generator能不能自动运行?当然能,并且有很多这样的库,这里我们先自己实现一个简单的。

function run(g) {var it = g();// 利用递归进行迭代(function iterator(val) {var ret = it.next(val);// 如果没有结束if(!ret.done) {// 判断promiseif(typeof ret.value === 'object' && 'then' in ret.value) {ret.value.then(iterator);} else {iterator(ret.value);}}})();
}
复制代码

这样我们就能自动处理运行我们的generator了,当然我们这个很简单,没有任何错误处理,如何让多个generator同时运行,这其中涉及到如何进行控制权的转换问题。我写了一个简单的执行器Fo,其中包含了Kyle Simpson大神的一个ping-pong的例子,感兴趣的可以看下这里是传送门,当然能顺手star一下就更好了,see you next article ~O(∩_∩)O~。

参考链接

  • The Basics Of ES6 Generators
  • Diving Deeper With ES6 Generators
  • Going Async With ES6 Generators
  • Getting Concurrent With ES6 Generators

如何理解Generator相关推荐

  1. 理解 Generator 的执行

    Generator & yield 开局官宣:sec-generatoryield,这是对yield的介绍. 同样巴拉巴拉列了9条,将以上链接中的说明简化成3条: 1. 在GeneratorF ...

  2. 用python函数求素数_Python:用filter函数求素数 (再理解generator)

    目的:更熟悉应用generator. 素数定义: 素数:质数又称素数.一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数. 方法: 计算素数的一个方法是埃氏筛法: 首先,列出从2开 ...

  3. 理解Generator生成器对象

    Generator 对象(理解为生成器函数更好) 注意,Generator函数,不可当作构造函数 首先声明一个Generator 函数,以下均以 gen 为例 function* gen(){yiel ...

  4. JavaScript的Generator理解使用

    Generator理解 Generator 函数是 ES6 提供的一种异步编程解决方案,可以这么理解: 从语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态. 执行 ...

  5. 深入理解Python生成器(Generator)

    我们可以通过列表生成式简单直接地创建一个列表,但是受到内存限制,列表容量肯定是有限的.而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,而且如果我们仅仅需要访问前面几个元素,那后面绝大多 ...

  6. Generator函数的理解和使用

    Generator函数的理解和使用 Generator 函数是 ES6 提供的一种异步编程解决方案. 一.异步编程 1.所谓"异步",简单说就是一个任务分成两段,先执行第一段,然后 ...

  7. 理解 ES6 Generator 函数

    Generator函数是ES6引入的新型函数,用于异步编程,跟Promise对象联合使用的话会极大降低异步编程的编写难度和阅读难度. Generator函数跟普通函数的写法有非常大的区别: 一是,fu ...

  8. Generator的正确打开方式

    前两年大量的在写Generator+co,用它来写一些类似同步的代码 但实际上,Generator并不是被造出来干这个使的,不然也就不会有后来的async.await了 Generator是一个可以被 ...

  9. es6 中的generator函数控制流程

    Generator函数跟普通函数的写法有非常大的区别: 一是,function关键字与函数名之间有一个星号: 二是,函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是&qu ...

最新文章

  1. 幅度调制后的频率混叠
  2. CC2530学习路线-基础实验-串口通讯发送字符串(4 未完待续)
  3. PHP学习方向-进阶2(三)
  4. JSP读取My SQL数据乱码问题的解决
  5. Spring 的IOC容器系列的设计与实现:BeanFactory 和 ApplicationContext
  6. TransactionProducer(事务消息)
  7. 使用Jmeter性能测试注意点
  8. 插件开发之360 DroidPlugin源码分析(五)Service预注册占坑
  9. linux mysql 消失_Linux下修改mysql的root密码后数据库消失怎么处理
  10. d-s证据理论 matlab 完整代码
  11. Android扫描wifi二维码自动连接wifi
  12. 基于ENVI下的土地利用信息提取(三)
  13. 2020-02-28
  14. 新型旅游网站热地带SEO优化建议
  15. 巴黎报纸对拿破仑的描述
  16. C#VB.NET 合并PDF页面
  17. 第4-8课:方块消除游戏
  18. 【K8S实战】-超详细教程(二)
  19. linux安装桌面xmanager,Linux安装图形界面和Vnc与Xmanager服务
  20. 软件体系结构与软件架构解析

热门文章

  1. DPDK 中断机制(二十六)
  2. linux 界面工具 qt,Qt主窗口中的工具栏
  3. 基于布隆过滤器实现敏感词识别和过滤
  4. Exp2 后门原理与实践 20164323段钊阳
  5. 浅谈JavaScript错误
  6. 百度地图API快速调用,一键生成百度地图
  7. CI Weekly #7 | Instgram/Quora 等大公司如何做持续部署?
  8. sigsuspend sigprocmask函数的用法
  9. 玩转虚拟化VMWare之一: VMWare ESXi 5.0和vSphere Client安装和配置
  10. 500强公司面试的经典正确与错误回答对比!