yield next和yield* next之间到底有什么区别?为什么需要yield* next?经常会有人提出这个问题。虽然我们在代码中会尽量避免使用yield* next以减少新用户的疑惑,但还是经常会有人问到这个问题。为了体现自由,我们在koa框架内部使用了yield* next,但是为了避免引起混乱我们并不提倡这样做。

  相关文档,可以查看这里的说明harmony proposal.

yield委托(delegating)做了什么?

  假设有下面两个generator函数:

  1. function* outer() {
  2. yield 'open'
  3. yield inner()
  4. yield 'close'
  5. }
  6.  
  7. function* inner() {
  8. yield 'hello!'
  9. }

  通过调用函数outer()能产出哪些值呢?

  1. var gen = outer()
  2. gen.next() // -> 'open'
  3. gen.next() // -> a generator
  4. gen.next() // -> 'close'

  但如果我们把其中的yield inner()改成yield* inner(),结果又会是什么呢?

  1. var gen = outer()
  2. gen.next() // -> 'open'
  3. gen.next() // -> 'hello!'
  4. gen.next() // -> 'close'

  事实上,下面两个function本质上来说是等价的:

  1. function* outer() {
  2. yield 'open'
  3. yield* inner()
  4. yield 'close'
  5. }
  6.  
  7. function* outer() {
  8. yield 'open'
  9. yield 'hello!'
  10. yield 'close'
  11. }

  从这个意义上来说,委托的generator函数替代了yield*关键字的作用!

这与Co或Koa有什么关系呢?

  Generator函数已经很让人抓狂了,它并不能帮助Koa的generator函数使用Co来控制流程。很多人都会被本地的generator函数和Co框架提供的功能搞晕。

  假设有以下generator函数:

  1. function* outer() {
  2. this.body = yield inner
  3. }
  4.  
  5. function* inner() {
  6. yield setImmediate
  7. return 1
  8. }

  如果使用Co,它实际上等价于下面的代码:

  1. function* outer() {
  2. this.body = yield co(function inner() {
  3. yield setImmediate
  4. return 1
  5. })
  6. }

  但是如果我们使用yield委托,完全可以去掉Co的调用:

  1. function* outer() {
  2. this.body = yield* inner()
  3. }

  那么最终执行的代码会变成下面这样:

  1. function* outer() {
  2. yield setImmediate
  3. this.body = 1
  4. }

  每一个Co的调用都是一个闭包,因此它会或多或少地存在一些性能上的开销。不过你也不用太担心,这个开销不会很大,但是如果使用委托yield,我们就可以降低对第三方库的依赖而从代码级别避免这种开销。

有多快?

  这里有一个链接,是之前我们讨论过的有关该话题的内容:https://github.com/koajs/compose/issues/2. 你不会看到有太多的性能差异(至少在我们看来),特别是因为实际项目中的代码会显著地降低这些基准。因此,我们并不提倡使用yield* next,不过在内部使用也并没有坏处。

  有趣的是,通过使用yield* next,Koa比Express要快!Koa没有使用dispatcher(调度程序),这与Express不同。Express使用了许多的调度程序,如connect, router等。

  使用委托yield,Koa事实上将:

  1. co(function* () {
  2. var start = Date.getTime()
  3. this.set('X-Powered-By', 'koa')
  4. if (this.path === '/204')
  5. this.status = 204
  6. if (!this.status) {
  7. this.status = 404
  8. this.body = 'Page Not Found'
  9. }
  10. this.set('X-Response-Time', Date.getTime() - start)
  11. }).call(new Context(req, res))

  拆解成:

  1. app.use(function* responseTime(next) {
  2. var start = Date.getTime()
  3. yield* next
  4. this.set('X-Response-Time', Date.getTime() - start)
  5. })
  6.  
  7. app.use(function* poweredBy(next) {
  8. this.set('X-Powered-By', 'koa')
  9. yield* next
  10. })
  11.  
  12. app.use(function* pageNotFound(next) {
  13. yield* next
  14. if (!this.status) {
  15. this.status = 404
  16. this.body = 'Page Not Found'
  17. }
  18. })
  19.  
  20. app.use(function* (next) {
  21. if (this.path === '/204')
  22. this.status = 204
  23. })

  一个理想的Web application看起来就和上面这段代码差不多。在上面使用Co的那段代码中,唯一的开销就是启动单个co实例和我们自己的Context构造函数,方便起见,我们将node的req和res对象封装进去了。

使用它进行类型检查

  如果将yield*应用到不是generator的函数上,你将会看到下面的错误:

  1. TypeError: Object function noop(done) {
  2. done();
  3. } has no method 'next'

  这是因为基本上任何具有next方法的东西都被认为是一个generator函数!就我个人而言,我很喜欢这个,因为默认我会假设我就是一个yield* gen()。我重写了很多我的代码来使用generator函数,如果我看到某些东西没有被写成generator函数,那么我会想,我可以将它们转换成generator函数而使代码更简化吗?

  当然,这也许并不适用于所有人,你也许能找到其它你想要进行类型检查的原因。

上下文

  Co会在相同的上下文中调用所有连续的可yield的代码。如果你想在yield function中改变调用的上下文,会有些麻烦。看下面的代码:

  1. function Thing() {
  2. this.name = 'thing'
  3. }
  4.  
  5. Thing.prototype.print = function (done) {
  6. var self = this
  7. setImmediate(function () {
  8. console.log(self.name)
  9. })
  10. }
  11.  
  12. // in koa
  13. app.use(function* () {
  14. var thing = new Thing()
  15. this.body = yield thing.print
  16. })

  这里你会发现this.body是undefined,这是因为Co事实上做了下面的事情:

  1. app.use(function* () {
  2. var thing = new Thing()
  3. this.body = yield function (done) {
  4. thing.print.call(this, done)
  5. }
  6. })

  而这里的this指向的是Koa的上下文,而不是thing。

  上下文在JavaScript中很重要,在使用yield*时,可以考虑使用下面两种方法之一:

  1. yield* context.generatorFunction()
  2. yield context.function.bind(context)

  这样可以让你避开99%的generator函数的上下文问题,从而避免了使用yield context.method给你带来的困扰!

yield next和yield* next的区别的更多相关文章

  1. sleep、yield、wait、join的区别(阿里面试)

    1.  Thread.sleep(long) 和Thread.yield()都是Thread类的静态方法,在调用的时候都是Thread.sleep(long)/Thread.yield()的方式进行调 ...

  2. sleep、yield、wait、join的区别(阿里)

    只有runnable到running时才会占用cpu时间片,其他都会出让cpu时间片.线程的资源有不少,但应该包含CPU资源和锁资源这两类.sleep(long mills):让出CPU资源,但是不会 ...

  3. Enumerator yielder.yield 与 Proc.yield 区别

    最近看ruby cookbook遇到这个用法,google一下,这里原文解释 http://stackoverflow.com/questions/18865860/enumerator-yielde ...

  4. 深入理解yield(三):yield与基于Tornado的异步回调

    转自:http://beginman.cn/python/2015/04/06/yield-via-Tornado/ 作者:BeginMan 版权声明:本文版权归作者所有,欢迎转载,但未经作者同意必须 ...

  5. 12.C#yield return和yield break及实际应用小例(六章6.2-6.4)

    晚上好,各位.今天结合书中所讲和MSDN所查,聊下yield关键字,它是我们简化迭代器的关键. 如果你在语句中使用了yield关键字,则意味着它在其中出现的方法.运算符或get访问器是迭代器,通过使用 ...

  6. yield return 和 yield break

    //yield return 返回类型必须为 IEnumerable.IEnumerable<T>.IEnumerator 或 IEnumerator<T>. static I ...

  7. C#yield return和yield break

    C#yield return和yield break 晚上好,各位.今天结合书中所讲和MSDN所查,聊下yield关键字,它是我们简化迭代器的关键. 如果你在语句中使用了yield关键字,则意味着它在 ...

  8. 线程之sleep(),wait(),yield(),join()等等的方法的区别

    操作线程的常用方法大体上有sleep(),join(),yield()(让位),wait(),notify(),notifyAll(),关键字synchronized等等.    由于这些方法功能有些 ...

  9. 线程中的sleep()、join()、yield()方法有什么区别?

    sleep().join().yield()有什么区别? sleep() sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优 ...

随机推荐

  1. opencv批处理提取图像的特征

    ____________________________________________________________________________________________________ ...

  2. MySQL Study之--MySQL schema_information数据库

    MySQL Study之--MySQL schema_information数据库       information_schema数据库是在mysql的版本号5.0之后产生的,一个虚拟数据库,物理上 ...

  3. ASP.MVC当URL跳转时候参数的安全性

    一个页面跳转到另外一个页面直接将参数写在URL上面并不安全比如 http://XXXXXXXXXXX/meeting/shakeGroup?id=5381&uid=o0En_sj1J0bFgI ...

  4. mysql默认安装目录说明

    MySQL安装完成后不象SQL Server默认安装在一个目录,它的数据库文件.配置文件和命令文件分别在不同的目录,了解这些目录非常重要,尤其对于Linux的初学者,因为 Linux本身的目录结构就比 ...

  5. java基础数据类型包装类

    */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...

  6. 理解spread运算符与rest参数

    理解spread运算符与rest参数 spread运算符与rest参数 是ES6的新语法.它们的作用是什么?能做什么事情? 1. rest运算符用于获取函数调用时传入的参数. function tes ...

  7. .NET开发一个微信跳一跳辅助程序

    昨天微信更新了,出现了一个小游戏"跳一跳",玩了一下 赶紧还蛮有意思的 但纯粹是拼手感的,玩了好久,终于搞了个135分拿了个第一名,没想到过一会就被朋友刷下去了,最高的也就200来 ...

  8. PHP-无限级分类(迭代法创建)

    $area = array( array('id'=>1,'name'=>'安徽','parent'=>0), array('id'=>2,'name'=>'海淀','p ...

  9. Linux Centos 使用 yum 安装java

    centos 使用 yum 安装java 首先,在你的服务器上运行一下更新. yum update 然后,在您的系统上搜索,任何版本的已安装的JDK组件. rpm -qa | grep -E '^op ...

  10. split 命令详解

    作用:将大文件切割成小文件. 参数:-l 按照行数分隔文件       -b 按照大小分隔文件       -d 使用数字做后缀 实例:分隔文件默认1000行     split mylog ; wc ...