生成器是组织异步代码的强大工具,但它们不会神奇地等待异步代码运行。
发生了什么
让我们浏览一下您的代码,以便您了解发生了什么:
getRelationsList 是一个生成器函数,调用时会返回一个新的生成器。此时,您的生成器函数中没有任何代码被调用(尽管如果您传递参数,它们将被设置)。然后在生成器上调用.next 以开始执行生成器函数。它将一直执行,直到遇到第一个 yield 语句并返回一个带有产生值和生成器完成状态的对象。
到目前为止,您似乎了解了大部分内容,但是生成器并没有神奇地转换产生的值。当您输出db.relations.find({}) 时,您将获得find 函数的返回值,我假设它是Promise 或某种类型的thenable:
所以你的“外部”值是{ value:Promise, done:false }
您的内部console.log 从未运行过的原因是,您实际上每次调用getRelationsList() 时都在创建一个新生成器,所以当您在外部console.log 之后再次调用getRelationsList().next() 时,您正在创建一个新的生成器generator 并调用 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。
这是一个演示所有这些的链接:http://jsfiddle.net/qj1aszub/2/
解决方案
koa 的创建者使用了一个名为 co 的小库,它基本上会提取出 promise 并等待它们完成,然后再将解析的值传递回生成器函数(使用 .next() 函数),以便您可以编写同步风格的异步代码。
co 将返回一个 promise,这将要求您调用 .then 方法以获取从生成器函数返回的值。
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 包装事物并处理承诺,直到您处于某种“顶级”:
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)
})