I was experimenting using genrator functions, and I came up with the following generator. It works, but I don't understand how it is able to use the yield input, process it and use the same yield statement to output the result...
How does the program flow work?
//My caching Generator
function* cache(){
let input, output, cache = {};
while(true){
input = yield output;
//Some code...
if(input in cache){
output = cache[input];
console.log("Old Value: "+output);
}else{
output = f(input);
cache[input] = output;
console.log("New Value: "+output);
}
}
}
//Some expensive function:
function f(x){
return x.split("").reverse().join("");
}
const c = cache();
console.log("Return value: "+ c.next("this is lost").value);
console.log("Return value: "+ c.next("Hello").value);
console.log("Return value: "+ c.next("World").value);
console.log("Return value: "+ c.next("Hello").value);
console.log("Return value: "+ c.next("Stackoverflow").value);
I'll give it a try:
const c = cache();
console.log("Return value: "+ c.next("this is lost").value);
This assigns c with an iterator function for cache (first line) and then runs cache initially until the first yield expression, so basically this code:
let input, output, cache = {};
yield output;
output is undefined, so undefined is returned from the first next() call. Note, however, that the argument ("this is lost") is not passed into this yield. It'd be passed into the yield where the previous execution was halted. As there was no previous run of c.next it is, indeed, lost.
In the next run:
console.log("Return value: "+ c.next("Hello").value);
the following code is executed:
input = "Hello"; // the yield is substituted with the next() argument here!
//Some code...
if(input in cache){ // false
output = cache[input];
console.log("Old Value: "+output);
} else {
output = f(input);
cache[input] = output;
console.log("New Value: "+output);
}
yield output // "olleH"
The function resumes at the exact line where it was halted before and the yield output gets replaced with the next() argument. MDN says:
Calling the next() method with an argument will resume the generator function execution, replacing the yield expression where execution was paused with the argument from next().
Related
function* test(action) {
const subgenerator = function*() {
const subgeneratorVariable = yield '1';
console.log('subgeneratorVariable', subgeneratorVariable);
};
for (const generatedValue of subgenerator()) {
const result = yield generatedValue;
console.log('result', result);
}
}
const gen = test();
console.log(gen.next());
console.log(gen.next('a'));
This returns:
{value: "1", done: false}
result a
subgeneratorVariable undefined
{value: undefined, done: true}
So I don't have access to yield results in the nested generator. However!
In this case:
function* test(action) {
const subgenerator = function*() {
const subgeneratorVariable = yield '1';
console.log('subgeneratorVariable', subgeneratorVariable);
const subgeneratorVariable2 = yield '2';
console.log('subgeneratorVariable2', subgeneratorVariable2);
};
const result = yield* subgenerator();
console.log('result', result);
}
const gen = test();
console.log(gen.next());
console.log(gen.next('1'));
console.log(gen.next('2'));
The result is:
{value: "1", done: false}
subgeneratorVariable a
{value: "2", done: false}
subgeneratorVariable2 b
result undefined
{value: undefined, done: true}
So the situation is reversed. Now I have access to yielded value ONLY in nested generator.
Is there any way of passing values/results between these two generators?
yield is going to return what you are passing to next as a parameter, not the expression at right side; for...of internally is calling next without parameters so you are getting undefined.
Maybe this example helps you to understand with a do...while instead of for...of:
function* test(action) {
const subgenerator = function*() {
let subgeneratorVariable;
subgeneratorVariable = yield '1';
console.log("subgeneratorVariable: " + subgeneratorVariable);
subgeneratorVariable = yield '2';
console.log("subgeneratorVariable: " + subgeneratorVariable);
};
const sub = subgenerator();
let value;
do {
value = yield sub.next(value).value;
console.log("value: " + value);
} while(value);
}
const gen = test();
let value;
value = gen.next().value;
value = gen.next(value).value;
value = gen.next(value).value;
The result is:
value: 1
subgeneratorVariable: 1
value: 2
subgeneratorVariable: 2
As you can see, if you want to keep the values between the generators you must pass the result values to the generators next function.
After upgrading myself with the new yield in javascript and some efforts reading your examples, I think I figured out what you are trying to do and what you are doing wrong.
Your 1st attempt was correct. But you placed your console.log incorrectly which mislead you into thinking it was wrong.
I rewrote it so you get better insight of what happens when yield-ing.
function* generator() {
function* subgenerator() {
yield '1';
yield '2';
};
for (let generatedValue of subgenerator()) {
// Use generatedValue here ...
console.log("Inside generator:",generatedValue);
// .. and finally yield it to your main program
yield generatedValue;
}
}
let gen = generator();
console.log("Outside generator:",gen.next());
console.log("Outside generator:",gen.next());
console.log("Outside generator:",gen.next());
In your second example, you delegate the generation to the subgenerator. Doing so, you cannot get the value in your generator itself. Because the value is yield-ed and your generator will resume after the subgenerator is finished generating. It explains why, your generator only sees undefined.
I'm using generator functions in javascript for the first time and running in to some interesting problems.
Code:
import moment from 'moment';
export default function recur(quantity, units) {
console.log('TESTING 1');
function* recurGenerator(startDate, maxDate) {
console.log('TESTING 2');
if (maxDate === undefined) {
this.throw('Argument maxDate is undefined');
}
let nextDate = moment(startDate).clone();
maxDate = moment(maxDate);
for (;;) {
nextDate = moment(nextDate).clone().add(quantity, units);
if (nextDate.isAfter(maxDate)) yield null;
yield nextDate;
}
}
return recurGenerator;
}
The "TESTING 2" console.log never gets called. It also doesn't raise an error if I don't pass maxDate to the generator function. This must be something about generator's I'm missing.
EDIT TO SHOW USAGE
recur(1, 'day')(moment())
It seems that next needs to be called to run code before the first yield?
In a generator function, the code before the first yield statement gets executed not sooner than the generator proceeds to that point:
let a = function * () {
console.log(1);
yield 2;
yield 3;
}
let b = a(); // no console output!
let c = b.next(); // prints 1 to the console
c // { value: 2, done: false }
I am having trouble finding out what happens if you use a return statement instead of yield.
function *gen(){
const val = yield someAsyncFn();
assert.equal(val,4);
return val;
}
how does the return behave differently from the yield? I assume the return acts as a normal return statement, but the context of a generator function, does it also call gen.return() as well? Sort of confusing.
Perhaps the above is merely identical to this?
function *gen(){
const val = yield someAsyncFn();
assert.equal(val,4);
yield val;
}
return deliveres a return value for an iterators last iteration (when done equals true).
I've simplified your example a bit, since the async operation doesn't seem to be relevant to the question:
function *gen(){
const val = yield 4;
return val * 2;
}
var it = gen();
var val = it.next(); // { value: 4, done: false }
console.log(val.value); // 4
var res = it.next(val.value); // { value: 8, done: true }
console.log(res.value); // 8
Whereas without a return value, on the last iteration you will return a value of undefined:
function *gen2(){
const val = yield 4;
yield val * 2;
}
var it2 = gen2();
var val2 = it2.next(); // { value: 4, done: false }
console.log(val2.value); // 4
var res2 = it2.next(val2.value); // { value: 8, done: false }
console.log(res2.value); // 8
it2.next(); // { value: undefined, done: true }
Sidenote: As a rule of thumb, there is always one more next call then there are yield statements, which is why there is one more next call in the second example.
Let's say you're using a generator-runner like co, then the value you get after finishing the generator would be the value you return:
co(function* () {
var result = yield Promise.resolve(true);
return result;
}).then(function (value) {
console.log(value); // value equals result
}, function (err) {
console.error(err.stack); // err equals result
});
Important: If you are iterating through an iterator, using a for ... of loop or something like Array.from, the return value is going to be ignored (Since you are doing async operations, this is probably not the case anyway):
function *gen(){
const val = yield 4;
return val * 2;
}
for (let value of gen()) {
console.log(value);
}
// 4
In the end, calling a generator just creates an iterator. Whether the final value that the iterator returns is relevant, depends entirely on how you use it.
In addition to the thorough answer by #nils, there is one additional way to capture the return value of a generator function, namely as the value of yield* (necessarily inside another generator function):
function* arrayGenerator(arr) {
for (const element of arr)
yield element
return arr.length
}
function* elementsFollowedByLength(arr) {
const len = yield* arrayGenerator(arr);
yield len;
}
Note the first generator function which returns a value, after it is done yielding the array elements.
The second generator function, through yield*, causes the first generator function to yield all its values. When the first generator function is done and returns, that return value becomes the value of the yield* expression.
I'm experimenting with ES6's generator functions and yield statements, Example.
function run(generator) {
var itr = generator(resume);
function resume(callbackValue) {
itr.next(callbackValue);
}
itr.next();
}
function* main(resume) {
var result1 = yield add(1, resume);
var data1 = result1;
console.log("add 1 = ", data1)
var data2 = yield add(1, resume);
console.log("add 1 = ", data2);
var data3 = yield add(data1, resume);
console.log("add data1 =", data3);
console.log("total is ", data1 + data2 + data3);
}
function add(num, resume) {
setTimeout(function() {
resume(num + 1);
}, 0);
}
run(main);
I plan on using yield as flow control for asynchronous REST calls, where the request will call next once it has a response, but for now I'm just using a simple adding function. It works as planned which is exciting but resume will only work with in the setTimeout and I'm not sure why.
If it just have:
function add (num, resume) {
resume(num + 1);
}
the interpreter gives me 'Generator is already running'.
There doesn't need to be an actual wait in the time for the timeout, and I also tried a self invoking function, but that didn't help. Why does itr.next() need a timeout?
As other commenters said, you're trying to call resume while the yield expression is still resolving. Asynchronously calling itr.next will allow the yield expression to finish, and then immediately call itr.next when it's done. To get it to work the way you want, just change run:
function run(generator) {
var itr = generator(resume);
function resume(callbackValue) {
setTimeout(function(){
itr.next(callbackValue);
}, 0);
}
itr.next();
}
This is a really fun idea, and it could actually be useful.
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.
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)
})
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
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