KOA 中的发电机

Generators in KOA

在 KOA 中 app.use 如何工作? 当我在 app.use 中设置一些生成器时,一切正常。 我怎样才能在其他地方做同样的事情?

当我刚刚执行生成器手册时:

var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}
console.log({'outside: ': getRelationsList().next()});
getRelationsList().next();

我得到的只是 { 'outside: ': { value: [Function], done: false } }

这是我期望的:

{ 'outside: ': { value: {object_with_results}, done: false } }
{ 'inside: ': {object_with_results}

编辑

我这样修改了我的代码:

var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}
console.log({'outside ': co(getRelationsList)});

现在在控制台日志中显示的结果不错,但在控制台日志外部显示的只是空对象。

生成器必须由外部代码操作。 在引擎盖下,koa 使用 co 库来 'Run' 生成器。

以下是您如何在 koa 之外实现您想要的:

var co = require('co');
var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}

co(getRelationsList).catch(function(err){});

我在 JavaScript 生成器上做了一个简短的截屏视频,应该可以帮助您了解正在发生的事情:

http://knowthen.com/episode-2-understanding-javascript-generators/

++ 编辑

如果您使用生成器以更多的同步方式(消除回调)进行编程,那么您的所有工作都需要在生成器中完成,您应该使用像 co 这样的库来执行生成器。

这里有一个更详细的示例,说明您如何手动与生成器交互。这应该可以帮助您了解获得的结果。

function * myGenerator () {
  var a = yield 'some value';
  return a;
}
var iterator = myGenerator();
// above line just instantiates the generator
console.log(iterator);
// empty object returned
// {}

var res1 = iterator.next();
// calling next() start the generator to either the 
// first yield statement or to return.
console.log(res1);
// res1 is an object with 2 attributes 
// { value: 'some value', done: false }
// value is whatever value was to the right of the first 
// yield statment 
// done is an indication that the generator hasn't run
// to completion... ie there is more to do
var toReturn = 'Yield returned: ' + res1.value;
var res2 = iterator.next(toReturn);
// calling next(toReturn) passes the value of 
// the variable toReturn as the return of the yield 
// so it's returned to the variable a in the generator 
console.log(res2);
// res2 is an object with 2 attributes 
// { value: 'Yield returned: some value', done: true }
// no further yield statements so the 'value' is whatever
// is returned by the generator.
// since the generator was run to completion 
// done is returned as true

您的问题是您多次调用了 getRelationsList() 函数,这是不正确的。

将您的代码更改为以下内容

var g = getRelationsList();
console.log('outside: ', g.next());
g.next(); //You get your console.log('inside: .... here

生成器是组织异步代码的强大工具,但它们不会神奇地等待异步代码运行。

发生了什么事

让我们浏览一下您的代码,以便您了解发生了什么:

getRelationsList 是一个生成器函数,当被调用时 return 是一个新的生成器。此时,您的生成器函数中没有任何代码被调用(尽管如果您传递参数,它们将被设置)。然后在生成器上调用 .next 以开始执行生成器函数。它将一直执行到它遇到第一个 yield 语句和 return 一个具有产生值和生成器完成状态的对象。

到目前为止,您似乎已经了解了其中的大部分内容,但是生成器并不会神奇地 t运行 形成产生的值。当你退出 db.relations.find({}) 时,你将得到 find 函数的 return 值,我假设它是一个 Promise 或某种类型的 thenable:

所以你的 'outside' 值为 { value:Promise, done:false }

你的内部 console.log 从来没有 运行 的原因是你每次调用 getRelationsList() 时实际上都在创建一个新的生成器,所以当你再次调用 getRelationsList().next()在外部 console.log 之后,您将创建一个新的生成器并调用 next,因此它只会执行到第一个 yield,就像上一行中的调用一样。

为了完成执行,您必须在生成器的同一个实例上调用 next 两次:一次执行到 yield,一次继续执行到函数结束。

var gen = getRelationsList()
gen.next() // { value:Promise, done:false }
gen.next() // { value:undefined, done:true } (will also console.log inside)

你会注意到,但是,如果你 运行 这个,里面 console.log 将是 undefined。这是因为 yield 语句的值等于传递给以下 .next() 调用的值。

例如:

var gen2 = getRelationsList()
gen2.next() // { value:Promise, done:false }
gen2.next(100) // { value:undefined, done:true } 

产出

{ inside:100 }

因为我们将 100 传递给第二个 .next() 调用,它成为 yield db.relations.find({}) 语句的值,然后分配给 res.

这是一个 link 演示所有这些:http://jsfiddle.net/qj1aszub/2/

解决方案

koa 的创建者使用了一个名为 co 的小库,它基本上获取了 yielded out promises 并等待它们完成,然后再将已解析的值传回生成器函数(使用 .next() 函数) 以便您可以以同步方式编写异步代码。

co 将 return 一个承诺,这将要求您调用 .then 方法以从生成器函数中获取值 returned。

var co = require('co');
var getRelationsList = function *() {
    var res = yield db.relations.find({});
    console.log({'inside: ': res});
    return res
}

co(getRelationsList).then(function(res) {
    console.log({'outside: ': res })
}).catch(function(err){
    console.log('something went wrong')
});

co 还允许您产生其他生成器函数并等待它们完成,因此您不必在每个级别用 co 包装东西并处理承诺,直到您处于某种 'top level':

co(function *() {
    var list = yield getRelationsList()
      , processed = yield processRelations(list)
      , response = yield request.post('/some/api', { data:processed })
    return reponse.data
}).then(function(data) {
    console.log('got:', data)
})