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)
})
在 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)
})