I was viewing generator functions in Mozilla Dev page.
There was an example code which is having send() function.
function* fibonacci() {
var a = yield 1;
yield a * 2;
}
var it = fibonacci();
console.log(it); // "Generator { }"
console.log(it.next()); // 1
console.log(it.send(10)); // 20
console.log(it.close()); // undefined
console.log(it.next()); // throws StopIteration (as the generator is now closed)
But, both chrome and Firefox (Latest version) are throwing error on send() function.
Any views on this? Is it not supported?
.send is part of the Legacy generator objects which are specific to the SpiderMonkey engine. It will be removed in some future release. They have already started removing/replacing the legacy generator objects with ES6 generators in parts of their code (Bug 1215846, Bug 1133277)
For the moment you can still use legacy generators in Firefox (current version as of this answer: 43.0.4). Just leave off the * when defining, and as long as the function body uses a yield statement the legacy generator will be used.
function fibonacci() {
var a = yield 1;
yield a * 2;
}
var it = fibonacci();
console.log(it);
console.log(it.next());
console.log(it.send(10));
console.log(it.close());
console.log(it.next());
Interesting that in ESNext might be function.sent()
var result;
function* generator() {
result = function.sent;
}
var iter = generator();
iter.next('tromple');
return result === 'tromple';
https://github.com/allenwb/ESideas/blob/master/Generator%20metaproperty.md
Related
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
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
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)
});
I have a somewhat bigger computation (~0.5sec) in my node app, and I want to make it non-blocking without using a webworker. (I think a webworker would be a little overkill in this situation)
Is there a way to force a return to the main loop, in order to give node the chance to process another request?
It sounds like you're saying you want to do the calculation in bite-sized chunks, on the main thread, rather than spinning it off to its own thread (e.g., using webworker-threads or child processes or the like).
I can think of three options:
ES6 Generator functions.
A function that returns its state and lets you call it again with a state object (which is basically what ES6 generator functions are, but they give you much nicer syntax).
A function that continues running on its own by using nextTick, without your being in control of how it runs.
The third option offers the calling code the least control; the first and third are probably simplest to implement.
ES6 Generator Function
You can use ES6's generator functions in recent versions of NodeJS via the --harmony_generators flag. Generator functions can "yield" back to the calling code, which can then tell them to pick up where they left off later.
Here's an example of a simple generator that counts to a limit, adding one to the count each time you call it:
function* counter(start, inc, max) {
while (start < max) {
yield start;
start += inc;
}
}
var x = counter(1, 1, 10);
console.log(x.next().value); // 1
console.log(x.next().value); // 2
console.log(x.next().value); // 3
Note that that does not spin the calculation off to a different thread, it just lets you do a bit of it, do something else, then come back and do a bit more of it.
A function that returns and accepts its state
If you can't use generators, you can implement the same sort of thing, making all your local variables properties of an object, having the function return that object, and then having it accept it again as an argument:
function counter(start, inc, max) {
var state;
if (typeof start === "object") {
state = start;
if (!state.hasOwnProperty("value")) {
state.value = state.start;
} else if (state.value < state.max) {
state.value += state.inc;
} else {
state.done = true;
}
} else {
state = {
start: start,
inc: inc,
max: max,
done: false
};
}
return state;
}
var x = counter(1, 1, 10);
console.log(counter(x).value); // 1
console.log(counter(x).value); // 2
console.log(counter(x).value); // 3
You can see how generators simplify things a bit.
A function that runs without the calling code controlling it
Here's an example of a function that uses nextTick to perform its task in bite-sized pieces, notifying you when it's done:
function counter(start, inc, max, callback) {
go();
function go() {
var done = start >= max;
callback(start, done);
if (!done) {
++start;
process.nextTick(go);
}
}
}
counter(1, 1, 10, function(value, done) {
console.log(value);
});
Right now, you can't use generators at global scope (you probably don't want to anyway), you have to do it within a function in strict mode. (Even a global "use strict" won't do it.) This is because V8 is still getting its ES6 features...
Complete sample script for a current version of node (allowing for the above):
"use strict";
(function() {
function* counter(start, inc, max) {
while (start < max) {
yield start;
start += inc;
}
}
var x = counter(1, 1, 10);
console.log(x.next().value); // 1
console.log(x.next().value); // 2
console.log(x.next().value); // 3
})();
In node (0.11.9, with the --harmony flag), how do I restart a generator after it finishes?
I tried doing generator.send(true); but it says the send() method doesn't exists.
You don't restart a generator. Once it has completed, it has finished its run like any other function. You need to recreate the generator to run again.
var count = function*(){ yield 1; return 2;};
var gen = count();
var one = gen.next();
var two = gen.next();
// To run it again, you must create another generator:
var gen2 = count();
The other option would be to design your generator such that it never finishes, so you can continue calling it forever. Without seeing the code you are talking about, it is hard to make suggestions though.
A bit late, but this is just a FYI.
At the moment, the send method is not implemented in Node, but is in Nightly (FF) - and only in some way.
Nightly:
If you declare your generator without the *, you'll get an iterator that has a send method:
var g = function() {
var val = yield 1; // this is the way to get what you pass with send
yield val;
}
var it = g();
it.next(); // returns 1, note that it returns the value, not an object
it.send(2); // returns 2
Node & Nightly:
Now, with the real syntax for generators - function*(){} - the iterators you produce won't have a send method. BUT the behavior was actually implemented in the next method. Also, note that it was never intended for send(true); to automatically restart your iterator. You have to test the value returned by yield to manually restart it (see the example in the page you linked). Any value, as long as it's not a falsy one, could work. See for yourself:
var g = function*() {
var val = 1;
while(val = yield val);
}
var it = g();
it.next(); // {done: false, value: 1}
it.next(true); // {done: false, value: true}
it.next(2); // {done: false, value: 2}
it.next(0); // {done: true, value: undefined}