Better understanding javascript's yield - javascript

I have the following code in my Koa app:
exports.home = function *(next){
yield save('bar')
}
var save = function(what){
var response = redis.save('foo', what)
return response
}
But I get the following error: TypeError: You may only yield a function, promise, generator, array, or object, but the following object was passed: "OK"
Now, "ok" is the response from the redis server, which makes sense. But I cannot fully grasp the concept of generators for this kinds of functions. Any help?

You don't yield save('bar') because SAVE is synchronous. (Are you sure you want to use save?)
Since it's synchronous, you should change this:
exports.home = function *(next){
yield save('bar')
}
to this:
exports.home = function *(next){
save('bar')
}
and it will block execution until it's finished.
Almost all other Redis methods are asynchronous, so you would need to yield them.
For example:
exports.home = function *(next){
var result = yield redis.set('foo', 'bar')
}

Yield is supposed to be used inside a generator function according to the documentation.
The purpose is to return the result of an iteration to be used in the next iteration.
Like in this example (taken from the documentation):
function* foo(){
var index = 0;
while (index <= 2) // when index reaches 3,
// yield's done will be true
// and its value will be undefined;
yield index++;
}
var iterator = foo();
console.log(iterator.next()); // { value:0, done:false }
console.log(iterator.next()); // { value:1, done:false }
console.log(iterator.next()); // { value:2, done:false }
console.log(iterator.next()); // { value:undefined, done:true }

Related

Why to use while when it is always true?

I see most of the examples in redux-saga using while(true){}:
function* watcherSaga(){
while (true) {
yield something()
}
}
Can we not simply write?
function* watcherSaga(){
yield something()
}
Or, is there anything difference?
Take a look at the following example. One generator is never "done" and the other generator is done after the first (and only) yield.
function something() {
return Math.random();
}
function* watcherSaga1() {
while (true) {
yield something();
}
}
function* watcherSaga2() {
yield something();
}
const watcher1 = watcherSaga1();
const watcher2 = watcherSaga2();
console.log('watcher1: ', watcher1.next());
console.log('watcher1: ', watcher1.next());
console.log('watcher1: ', watcher1.next());
console.log('watcher2: ', watcher2.next());
console.log('watcher2: ', watcher2.next());
console.log('watcher2: ', watcher2.next());
With the while loop, the generator will continue to yield values forever. Without, it's just once.
When you have a need for some sequence (say, multiples of 7), a function like that can easily supply such a sequence to a consumer that imposes its own limits on how many values it needs.
Generators provide an extremely powerful way of structuring code, and have a way of permeating a design in some cases. They're particularly useful in the context of media generation, like p5.js, where there's lots of interacting iteration. Generators provide a particularly nice way to encapsulate that.
Generators returns an Iterator.
When you call next() on the iterator, it produces the return value and if the function comes to end, it will generate an undefined value and gets done.
Check the below snippet. Hope this one helps you.
function* watcherSaga() {
var i = 0;
while (true) {
yield i++;
}
}
const sagaIterator = watcherSaga();
console.log("**** with while() loop *****");
console.log(sagaIterator.next());
console.log(sagaIterator.next());
console.log(sagaIterator.next());
// You can keep calling sagaIterator.next() and it never gets "done" because of "while(true)"
function* watcherSagaWithoutWhile() {
var i = 0;
yield i++;
}
const sagaIteratorWİthoutWhite = watcherSagaWithoutWhile();
console.log("**** withOUT while() loop *****");
console.log(sagaIteratorWİthoutWhite.next());
console.log(sagaIteratorWİthoutWhite.next());
console.log(sagaIteratorWİthoutWhite.next());
// The second call to "next()" will return an "undefined" value and the generator gets done because the generator comes to the end
For further reading: https://basarat.gitbooks.io/typescript/docs/generators.html

Generator without yield

var A = {
demo : function() * {
/* Some logic here, but no yield is used */
}
}
What is the use of a generator method that does not yield anything?
Have you ever used something like this? What was the use case?
It's quite the same case like an empty function - someone wants to call a function, but you have nothing to do.
Similarly, an empty generator function is a function which creates a generator that does nothing. It does represent the empty sequence. However, a generator function that doesn't yield isn't necessarily empty - it can still do something and have a result value, but there simply are no intermediate results.
The following code prints 'someValue' on the response every 100 ms for 5 seconds. It does not use yield.
const Koa = require('koa');
const through = require('through');
(new Koa()).use(function *(){
const tr = through();
setInterval(() => tr.write('someValue\n'), 100);
setTimeout(tr.end, 5000);
this.body = tr;
}).listen(3003, () => {});
Access with: curl localhost:3003

Why won't yield return from within a `.map` callback?

Learn Generators - 4 » CATCH ERROR!
The solution uses a for loop but I just couldn't find anything in MDN - Iteration Protocols that refers to yield within callbacks.
I'm going to guess the answer is just don't do that but thanks in advance if anyone has the time or inclination to provide an explanation!
Code:
function *upper (items) {
items.map(function (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
}
}
var badItems = ['a', 'B', 1, 'c']
for (var item of upper(badItems)) {
console.log(item)
}
// want to log: A, B, null, C
Error:
⇒ learn-generators run catch-error-map.js
/Users/gyaresu/programming/projects/nodeschool/learn-generators/catch-error-map.js:4
yield item.toUpperCase() // error below
^^^^
SyntaxError: Unexpected identifier
at exports.runInThisContext (vm.js:73:16)
at Module._compile (module.js:443:25)
at Object.Module._extensions..js (module.js:478:10)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Function.Module.runMain (module.js:501:10)
at startup (node.js:129:16)
at node.js:814:3
Even my editor knows this is a terrible idea...
Disclaimer: I'm the author of Learn generators workshopper.
Answer by #slebetman is kinda correct and I also can add more:
Yes, MDN - Iteration Protocol doesn't refer directly about yield within callbacks.
But, it tell us about importance from where you yield item, because you can only use yield inside generators. See MDN - Iterables docs to find out more.
#marocchino suggest just fine solution iterate over Array that was changed after map:
function *upper (items) {
yield* items.map(function (item) {
try {
return item.toUpperCase();
} catch (e) {
return null;
}
});
}
We can do it, because Array has iteration mechanism, see Array.prototype[##iterator]().
var bad_items = ['a', 'B', 1, 'c'];
for (let item of bad_items) {
console.log(item); // a B 1 c
}
Array.prototype.map doesn't have default iteration behavior, so we couldn't iterate over it.
But generators is not just iterators. Every generator is an iterator, but not vice versa. Generators allows you to customize iteration (and not only) process by calling yield keyword. You can play and see the difference between generators/iterators here:
Demo: babel/repl.
One problem is yield yields just one level to the function's caller. So when you yield in a callback it may not do what you think it does:
// The following yield:
function *upper (items) { // <---- does not yield here
items.map(function (item) { // <----- instead it yields here
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
}
}
So in the code above, you have absolutely no access to the yielded value. Array.prototype.map does have access to the yielded value. And if you were the person who wrote the code for .map() you can get that value. But since you're not the person who wrote Array.prototype.map, and since the person who wrote Array.prototype.map doesn't re-yield the yielded value, you don't get access to the yielded value(s) at all (and hopefully they will be all garbage collected).
Can we make it work?
Let's see if we can make yield work in callbacks. We can probably write a function that behaves like .map() for generators:
// WARNING: UNTESTED!
function *mapGen (arr,callback) {
for (var i=0; i<arr.length; i++) {
yield callback(arr[i])
}
}
Then you can use it like this:
mapGen(items,function (item) {
yield item.toUpperCase();
});
Or if you're brave you can extend Array.prototype:
// WARNING: UNTESTED!
Array.prototype.mapGen = function *mapGen (callback) {
for (var i=0; i<this.length; i++) {
yield callback(this[i])
}
};
We can probably call it like this:
function *upper (items) {
yield* items.mapGen(function * (item) {
try {
yield item.toUpperCase()
} catch (e) {
yield 'null'
}
})
}
Notice that you need to yield twice. That's because the inner yield returns to mapGen then mapGen will yield that value then you need to yield it in order to return that value from upper.
OK. This sort of works but not quite:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value); // returns generator object
Not exactly what we want. But it sort of makes sense since the first yield returns a yield. So we process each yield as a generator object? Lets see:
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value.next().value.next().value); // works
console.log(u.next().value.next().value.next().value); // doesn't work
OK. Let's figure out why the second call doesn't work.
The upper function:
function *upper (items) {
yield* items.mapGen(/*...*/);
}
yields the return value of mapGen(). For now, let's ignore what mapGen does and just think about what yield actually means.
So the first time we call .next() the function is paused here:
function *upper (items) {
yield* items.mapGen(/*...*/); // <----- yields value and paused
}
which is the first console.log(). The second time we call .next() the function call continue at the line after the yield:
function *upper (items) {
yield* items.mapGen(/*...*/);
// <----- function call resumes here
}
which returns (not yield since there's no yield keyword on that line) nothing (undefined).
This is why the second console.log() fails: the *upper() function has run out of objects to yield. Indeed, it only ever yields once so it has only one object to yield - it is a generator that generates only one value.
OK. So we can do it like this:
var u = upper(['aaa','bbb','ccc']);
var uu = u.next().value; // the only value that upper will ever return
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
console.log(uu.next().value.next().value); // works
Yay! But, if this is the case, how can the innermost yield in the callback work?
Well, if you think carefully you'll realize that the innermost yield in the callback also behaves like the yield in *upper() - it will only ever return one value. But we never use it more than once. That's because the second time we call uu.next() we're not returning the same callback but another callback which in turn will also ever return only one value.
So it works. Or it can be made to work. But it's kind of stupid.
Conclusion:
After all this, the key point to realize about why yield doesn't work the way we expected is that yield pauses code execution and resumes execution on the next line. If there are no more yields then the generator terminates (is .done).
Second point to realize is that callbacks and all those Array methods (.map, .forEach etc.) aren't magical. They're just javascript functions. As such it's a bit of a mistake to think of them as control structures like for or while.
Epilogue
There is a way to make mapGen work cleanly:
function upper (items) {
return items.mapGen(function (item) {
try {
return item.toUpperCase()
} catch (e) {
return 'null'
}
})
}
var u = upper(['aaa','bbb','ccc']);
console.log(u.next().value);
console.log(u.next().value);
console.log(u.next().value);
But you'll notice that in this case we return form the callback (not yield) and we also return form upper. So this case devolves back into a yield inside a for loop which isn't what we're discussing.
You can use another method by "co - npm": co.wrap(fn*)
function doSomething(){
return new promise()
}
var fn = co.wrap(function* (arr) {
var data = yield arr.map((val) => {
return doSomething();
});
return data;
});
fn(arr).then(function (val) {
consloe.log(val)
});

Generator returns undefined for a value while waiting on promise to resolve

I have a function connectImpl referenced in multiple places. I am trying to invoke this promise and return its value out to the calling function synchronously through intermediating via a generator. If I call .next() on the generator it is returned in a pending state
{ value: { state: 'pending' }, done: false }
I would like to wait on the value of this generator until it is no longer pending. I have tried multiple versions of waitOn to accomplish this, but I cannot seem to make it work properly.
I am open to implementation suggestions. This is driving me a bit batty. Surprisingly the final created log of the promise fires later in the execution chain - after the generator is done. I am obviously missing something:
let models = null ;
let connectImpl = function() {
console.log('connectImpl')
let orm = setupImpl()
let config = getConfigImpl()
let qInitialize = q.nbind(orm.initialize, orm)
if(models) {
console.log('connectImpl:cached')
return q(models)
} else {
console.log('connectImpl:create')
return qInitialize(config).then(function(m){
console.log('connectImpl:created')
models = m
return models
})
}
}
let waitOn = function(generator){
console.log('waitOn')
let done = false ;
let generatorValue = null
while(!done){
var generatorResult = generator.next()
console.log(generatorResult)
done = generatorResult.done
generatorValue = generatorResult.value
}
return generatorValue
}
let domainImpl = function() {
console.log('domainImpl')
let getConnection = function *() {
console.log('domainImpl:getConnection')
yield connectImpl()
}
var generator = getConnection()
return waitOn(generator)
}
console.log('START')
console.log(domainImpl())
console.log('END')
I am able to invoke and get the
START
domainImpl
waitOn
domainImpl:getConnection
connectImpl
connectImpl:create
{ value: { state: 'pending' }, done: false }
{ value: undefined, done: true }
undefined
END
connectImpl:created
I am able to add execute the connectImpl promise to work with the middleware via this function - but I can't seem to adapt this to my above use case:
let domainMiddlewareImpl = function () {
return function *(next) {
let models = yield connectImpl()
this.request.models = models.collections;
this.request.connections = models.connections;
yield next
};
};
This looks fun. Let's see how we can yield promises. Our end goal is to write something like:
waitOn(function*(){
console.log("hello");
yield Q.delay(2000); // a placeholder, your calls in your example
console.log("World"); // this should run two seconds late.
});
Your issue here is that you're yielding them without waiting for them in advance. First of all, you can skip to the end for a 'ready' solution (don't!) and here is a fiddle of what we're making. Let's go through implementing waitOn with generators:
Let's start:
function waitOn(gen){
}
So, our function takes a generator, the first thing we'll have to do is invoke it since we need to execute the generator to get its results:
function waitOn(gen){
let sequence = gen(); // call the generator
}
Next, we'll want to wrap everything in a Promise since our waitOn will yield promises and return a promise for being done itself:
function waitOn(gen){
let sequence = gen(); // call the generator
return Promise.resolve(); // this is Q.resolve with Q
}
Now, what cases do we have:
The generator is done and returned a value - that is a return
The generator yielded a regular value and we do not have to wait for it
The generator yielded a promise and we have to wait for it. We also have to deal with exceptions (what if we yield a promise that rejects?)
So our basic structure is something like:
function waitOn(gen){
let sequence = gen(); // call the generator
return Promise.resolve().then(function cont(value){
let {value, done} = en.next(value); // get the next item
// depending on the case do what's appropriate
});
}
Note the destructuring assignment - I assume that's ok since your code has ES6 statements in it too. Note since this is the first call, value is undefined but generally we'll want to pass the value from our last call on. Now to handle the cases:
function waitOn(gen){
let sequence = gen(); // call the generator
return Promise.resolve().then(function cont(value){
let {done, value} = en.next(value); // get the next item
if(done) return value; // return case
if(!value || !value.then) return cont(value); // value case, recurse
return value.catch(e => gen.throw(e)).then(cont); // promise case
});
}
Note the .catch clause - we're throwing our code from the promise back to the generator for it to handle so we can try/catch the promises.
That's it! In 9 lines of JavaScript we've implemented generators for promises. Now to your code, you can yield any promise:
let conn = q.nBind(orm.initialize, orm);
waitOn(function*(){
console.log("Starting")
let handle = yield conn(config);
console.log("Handle created!", handle); // connected here
});
Happy coding and enjoy the power of coroutines. After we've had our fun - it's worth mentioning that Q already ships with Q.async and other newer promise libraries like Bluebird ship with their own (Bluebird has Promise.coroutine). If you're using a promise library - you can utilise those. This implementation works with native promises too.

Getting a promise's value via yield & co

I'm trying to figure out how to get the value of a promise via yield, possibly with "co":
function *(){
var someVar = yield functionThatReturnsAPromise();
}
The called function is not a generator, just a normal function. With the above, someVar == Promise, but I want the resolved value. Does co or some other library have a way of doing this?
Typically a yield acts returns the same value to its own paused execution (left hand side of the yield function) as to the calling function of the generator. In this simple example counting from 1 to 5 example the input of the yield is the output of the yield to the generator function as well as to the generator's execution path:
function* inc() {
var g = 0;
while (true) {
g = yield g + 1;
}
}
var incGen = inc();
for (i = incGen.next().value; i <= 5; i = incGen.next(i).value) {
console.log(i); // ^ input of generator is last output
}
However, the calling function may also call the generator, but replace the output the last yield with another value or even throw an exception to the generator's execution. In the case of promise a function that returns a promise, may yield the result of that promise instead of the promise itself. So in this case:
var someVar = yield functionThatReturnsAPromise();
^ output != ^ input
you want the yield to act as a function that takes a promise as an input and returns a resolved promise as an output to the generator function.
It so happens co is able to do exactly this for you. All you need to do is feed your generator function to the co function:
co(function *(){
var someVar = yield functionThatReturnsAPromise();
})
To better understand how this works, here is an example of a function, that does the same thing as co:
function async(makeGenerator){
return function (){
var generator = makeGenerator.apply(this, arguments)
function handle(result){
if (result.done) return result.value
return result.value.then(function (res){
return handle(generator.next(res)) // <- sets output of yield to the promise result
}, function (err){ // and returns input promise
return handle(generator.throw(err)) // <- throw promise exception to generator function
})
}
return handle(generator.next()) // <- first time call, input of yield is a promise
}
}
source is from Forbes Lindesay's now famous presentation about this concept
Yes, co (https://github.com/tj/co) can do that. You'll have to wrap parent function inside co call:
co(function *(){
var someVar = yield functionThatReturnsAPromise();
})()
someVar inside will become resolved value. If promise gets rejected, error can be cought with basic try {} catch (e) {} statements.

Categories