OKey
OKey

Reputation: 182

Generators in KOA

How does work app.use in KOA? When I set some generator inside app.use, everything works perfect. How can I do the same elsewhere?

When I just execute generator manual:

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

I'm getting just { 'outside: ': { value: [Function], done: false } }

This what I expect is:

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

EDIT

I changed my code like that:

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

Now inside console log show's me good results but outside console log shows me just empty object. enter image description here

Upvotes: 0

Views: 321

Answers (3)

mlrawlings
mlrawlings

Reputation: 741

Generators are a powerful tool for organizing asynchronous code, but they don't magically wait for asynchronous code to run.

What's Happening

Let's walk through your code so you can see what is happening:

getRelationsList is a generator function, that when called returns a new generator. At this point, no code in your generator function has been called (although if you were passing params they would be set). You then call .next on your generator to start execution of the generator function. It will execute up until it hits the first yield statement and return an object with the yielded value and the completion status of the generator.

It seems you understand most of that so far, but generators do not magically transform the yielded out values. When you yield out db.relations.find({}), you'll get the return value of the find function which I'm assuming is a Promise or some type of thenable:

so your 'outside' value is { value:Promise, done:false }

The reason your inside console.log never ran is that you're actually creating a new generator each time you call getRelationsList(), so when you call getRelationsList().next() again after the outside console.log you're creating a new generator and calling next, so it only executes up to the first yield, just like the call on the previous line.

In order to finish execution you must call next twice on the same instance of your generator: once to execute up to the yield and once to continue execution to the end of the function.

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

You'll notice, however, if you run this, the inside console.log will be undefined. That's because the value of a yield statement is equal to the value passed to the following .next() call.

For example:

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

Outputs

{ inside:100 }

Because we passed 100 to the second .next() call and that became the value of the yield db.relations.find({}) statement which was then assigned to res.

Here's a link demoing all of this: http://jsfiddle.net/qj1aszub/2/

The Solution

The creators of koa use a little library called co which basically takes yielded out promises and waits for them to complete before passing the resolved value back into the generator function (using the .next() function) so that you can write your asynchronous code in a synchronous style.

co will return a promise, which will require you to call the .then method on to get the value returned from the generator function.

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 also allows you to yield out other generator functions and wait for their completion, so you don't have to wrap things with co at every level and deal with promises until you're at some sort of '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)
})

Upvotes: 4

James Moore
James Moore

Reputation: 1901

Generators must be acted upon by outside code. Under the hood koa use the co library to 'Run' the generator.

Here is how you might achieve what your wanting outside of koa:

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

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

I did a short screencast on JavaScript generators that should help you understand what's going on:

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

++ EDIT

If your using generators to program in more of an synchronous style (eliminating callbacks), then all your work needs to be done in the generator and you should use a library like co to execute the generator.

Here is a more detailed example of how you would interact with a generator, manually. This should help you understand the results your getting.

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

Upvotes: 1

Molda
Molda

Reputation: 5704

Your problem is that you call getRelationsList() function multiple times which is incorrect.

Change your code to following

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

Upvotes: 1

Related Questions