yield next和yield* next的区别

  yield next和yield* next之间到底有什么区别?为何须要yield* next?常常会有人提出这个问题。虽然咱们在代码中会尽可能避免使用yield* next以减小新用户的疑惑,但仍是常常会有人问到这个问题。为了体现自由,咱们在koa框架内部使用了yield* next,可是为了不引发混乱咱们并不提倡这样作。php

  相关文档,能够查看这里的说明harmony proposal.node

yield委托(delegating)作了什么?

  假设有下面两个generator函数:git

function* outer() {
  yield 'open'
  yield inner()
  yield 'close'
}

function* inner() {
  yield 'hello!'
}

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

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

  但若是咱们把其中的yield inner()改为yield* inner(),结果又会是什么呢?闭包

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

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

function* outer() {
  yield 'open'
  yield* inner()
  yield 'close'
}

function* outer() {
  yield 'open'
  yield 'hello!'
  yield 'close'
}

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

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

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

  假设有如下generator函数:ecmascript

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

function* inner() {
  yield setImmediate
  return 1
}

  若是使用Co,它实际上等价于下面的代码:函数

function* outer() {
  this.body = yield co(function inner() {
    yield setImmediate
    return 1
  })
}

  可是若是咱们使用yield委托,彻底能够去掉Co的调用:

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

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

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

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

有多快?

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

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

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

co(function* () {
  var start = Date.getTime()
  this.set('X-Powered-By', 'koa')
  if (this.path === '/204')
    this.status = 204
  if (!this.status) {
    this.status = 404
    this.body = 'Page Not Found'
  }
  this.set('X-Response-Time', Date.getTime() - start)
}).call(new Context(req, res))

  拆解成:

app.use(function* responseTime(next) {
  var start = Date.getTime()
  yield* next
  this.set('X-Response-Time', Date.getTime() - start)
})

app.use(function* poweredBy(next) {
  this.set('X-Powered-By', 'koa')
  yield* next
})

app.use(function* pageNotFound(next) {
  yield* next
  if (!this.status) {
    this.status = 404
    this.body = 'Page Not Found'
  }
})

app.use(function* (next) {
  if (this.path === '/204')
    this.status = 204
})

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

使用它进行类型检查

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

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

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

  固然,这也许并不适用于全部人,你也许能找到其它你想要进行类型检查的缘由。

上下文

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

function Thing() {
  this.name = 'thing'
}

Thing.prototype.print = function (done) {
  var self = this
  setImmediate(function () {
    console.log(self.name)
  })
}

// in koa
app.use(function* () {
  var thing = new Thing()
  this.body = yield thing.print
})

  这里你会发现this.body是undefined,这是由于Co事实上作了下面的事情:

app.use(function* () {
  var thing = new Thing()
  this.body = yield function (done) {
    thing.print.call(this, done)
  }
})

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

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

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

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

相关文章
相关标签/搜索